1# Copyright (C) 2005-2012 Canonical Ltd 2# Copyright (C) 2017 Breezy Developers 3# 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. 8# 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. 13# 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 17 18from io import BytesIO 19import sys 20 21from ..lazy_import import lazy_import 22lazy_import(globals(), """ 23from breezy import ( 24 cache_utf8, 25 config as _mod_config, 26 lockable_files, 27 lockdir, 28 rio, 29 shelf, 30 ) 31from breezy.bzr import ( 32 tag as _mod_tag, 33 ) 34""") 35 36from . import bzrdir 37from .. import ( 38 controldir, 39 errors, 40 revision as _mod_revision, 41 urlutils, 42 ) 43from ..branch import ( 44 Branch, 45 BranchFormat, 46 BranchWriteLockResult, 47 format_registry, 48 UnstackableBranchFormat, 49 ) 50from ..decorators import ( 51 only_raises, 52 ) 53from ..lock import _RelockDebugMixin, LogicalLockResult 54from ..trace import ( 55 mutter, 56 ) 57 58 59class BzrBranch(Branch, _RelockDebugMixin): 60 """A branch stored in the actual filesystem. 61 62 Note that it's "local" in the context of the filesystem; it doesn't 63 really matter if it's on an nfs/smb/afs/coda/... share, as long as 64 it's writable, and can be accessed via the normal filesystem API. 65 66 :ivar _transport: Transport for file operations on this branch's 67 control files, typically pointing to the .bzr/branch directory. 68 :ivar repository: Repository for this branch. 69 :ivar base: The url of the base directory for this branch; the one 70 containing the .bzr directory. 71 :ivar name: Optional colocated branch name as it exists in the control 72 directory. 73 """ 74 75 def __init__(self, _format=None, 76 _control_files=None, a_controldir=None, name=None, 77 _repository=None, ignore_fallbacks=False, 78 possible_transports=None): 79 """Create new branch object at a particular location.""" 80 if a_controldir is None: 81 raise ValueError('a_controldir must be supplied') 82 if name is None: 83 raise ValueError('name must be supplied') 84 self.controldir = a_controldir 85 self._user_transport = self.controldir.transport.clone('..') 86 if name != u"": 87 self._user_transport.set_segment_parameter( 88 "branch", urlutils.escape(name)) 89 self._base = self._user_transport.base 90 self.name = name 91 self._format = _format 92 if _control_files is None: 93 raise ValueError('BzrBranch _control_files is None') 94 self.control_files = _control_files 95 self._transport = _control_files._transport 96 self.repository = _repository 97 self.conf_store = None 98 Branch.__init__(self, possible_transports) 99 self._tags_bytes = None 100 101 def __str__(self): 102 return '%s(%s)' % (self.__class__.__name__, self.user_url) 103 104 __repr__ = __str__ 105 106 def _get_base(self): 107 """Returns the directory containing the control directory.""" 108 return self._base 109 110 base = property(_get_base, doc="The URL for the root of this branch.") 111 112 @property 113 def user_transport(self): 114 return self._user_transport 115 116 def _get_config(self): 117 """Get the concrete config for just the config in this branch. 118 119 This is not intended for client use; see Branch.get_config for the 120 public API. 121 122 Added in 1.14. 123 124 :return: An object supporting get_option and set_option. 125 """ 126 return _mod_config.TransportConfig(self._transport, 'branch.conf') 127 128 def _get_config_store(self): 129 if self.conf_store is None: 130 self.conf_store = _mod_config.BranchStore(self) 131 return self.conf_store 132 133 def _uncommitted_branch(self): 134 """Return the branch that may contain uncommitted changes.""" 135 master = self.get_master_branch() 136 if master is not None: 137 return master 138 else: 139 return self 140 141 def store_uncommitted(self, creator): 142 """Store uncommitted changes from a ShelfCreator. 143 144 :param creator: The ShelfCreator containing uncommitted changes, or 145 None to delete any stored changes. 146 :raises: ChangesAlreadyStored if the branch already has changes. 147 """ 148 branch = self._uncommitted_branch() 149 if creator is None: 150 branch._transport.delete('stored-transform') 151 return 152 if branch._transport.has('stored-transform'): 153 raise errors.ChangesAlreadyStored 154 transform = BytesIO() 155 creator.write_shelf(transform) 156 transform.seek(0) 157 branch._transport.put_file('stored-transform', transform) 158 159 def get_unshelver(self, tree): 160 """Return a shelf.Unshelver for this branch and tree. 161 162 :param tree: The tree to use to construct the Unshelver. 163 :return: an Unshelver or None if no changes are stored. 164 """ 165 branch = self._uncommitted_branch() 166 try: 167 transform = branch._transport.get('stored-transform') 168 except errors.NoSuchFile: 169 return None 170 return shelf.Unshelver.from_tree_and_shelf(tree, transform) 171 172 def is_locked(self): 173 return self.control_files.is_locked() 174 175 def lock_write(self, token=None): 176 """Lock the branch for write operations. 177 178 :param token: A token to permit reacquiring a previously held and 179 preserved lock. 180 :return: A BranchWriteLockResult. 181 """ 182 if not self.is_locked(): 183 self._note_lock('w') 184 self.repository._warn_if_deprecated(self) 185 self.repository.lock_write() 186 took_lock = True 187 else: 188 took_lock = False 189 try: 190 return BranchWriteLockResult( 191 self.unlock, 192 self.control_files.lock_write(token=token)) 193 except BaseException: 194 if took_lock: 195 self.repository.unlock() 196 raise 197 198 def lock_read(self): 199 """Lock the branch for read operations. 200 201 :return: A breezy.lock.LogicalLockResult. 202 """ 203 if not self.is_locked(): 204 self._note_lock('r') 205 self.repository._warn_if_deprecated(self) 206 self.repository.lock_read() 207 took_lock = True 208 else: 209 took_lock = False 210 try: 211 self.control_files.lock_read() 212 return LogicalLockResult(self.unlock) 213 except BaseException: 214 if took_lock: 215 self.repository.unlock() 216 raise 217 218 @only_raises(errors.LockNotHeld, errors.LockBroken) 219 def unlock(self): 220 if self.control_files._lock_count == 1 and self.conf_store is not None: 221 self.conf_store.save_changes() 222 try: 223 self.control_files.unlock() 224 finally: 225 if not self.control_files.is_locked(): 226 self.repository.unlock() 227 # we just released the lock 228 self._clear_cached_state() 229 230 def peek_lock_mode(self): 231 if self.control_files._lock_count == 0: 232 return None 233 else: 234 return self.control_files._lock_mode 235 236 def get_physical_lock_status(self): 237 return self.control_files.get_physical_lock_status() 238 239 def set_last_revision_info(self, revno, revision_id): 240 if not revision_id or not isinstance(revision_id, bytes): 241 raise errors.InvalidRevisionId( 242 revision_id=revision_id, branch=self) 243 revision_id = _mod_revision.ensure_null(revision_id) 244 with self.lock_write(): 245 old_revno, old_revid = self.last_revision_info() 246 if self.get_append_revisions_only(): 247 self._check_history_violation(revision_id) 248 self._run_pre_change_branch_tip_hooks(revno, revision_id) 249 self._write_last_revision_info(revno, revision_id) 250 self._clear_cached_state() 251 self._last_revision_info_cache = revno, revision_id 252 self._run_post_change_branch_tip_hooks(old_revno, old_revid) 253 254 def basis_tree(self): 255 """See Branch.basis_tree.""" 256 return self.repository.revision_tree(self.last_revision()) 257 258 def _get_parent_location(self): 259 _locs = ['parent', 'pull', 'x-pull'] 260 for l in _locs: 261 try: 262 contents = self._transport.get_bytes(l) 263 except errors.NoSuchFile: 264 pass 265 else: 266 return contents.strip(b'\n').decode('utf-8') 267 return None 268 269 def get_stacked_on_url(self): 270 raise UnstackableBranchFormat(self._format, self.user_url) 271 272 def set_push_location(self, location): 273 """See Branch.set_push_location.""" 274 self.get_config().set_user_option( 275 'push_location', location, 276 store=_mod_config.STORE_LOCATION_NORECURSE) 277 278 def _set_parent_location(self, url): 279 if url is None: 280 self._transport.delete('parent') 281 else: 282 if isinstance(url, str): 283 url = url.encode('utf-8') 284 self._transport.put_bytes('parent', url + b'\n', 285 mode=self.controldir._get_file_mode()) 286 287 def unbind(self): 288 """If bound, unbind""" 289 with self.lock_write(): 290 return self.set_bound_location(None) 291 292 def bind(self, other): 293 """Bind this branch to the branch other. 294 295 This does not push or pull data between the branches, though it does 296 check for divergence to raise an error when the branches are not 297 either the same, or one a prefix of the other. That behaviour may not 298 be useful, so that check may be removed in future. 299 300 :param other: The branch to bind to 301 :type other: Branch 302 """ 303 # TODO: jam 20051230 Consider checking if the target is bound 304 # It is debatable whether you should be able to bind to 305 # a branch which is itself bound. 306 # Committing is obviously forbidden, 307 # but binding itself may not be. 308 # Since we *have* to check at commit time, we don't 309 # *need* to check here 310 311 # we want to raise diverged if: 312 # last_rev is not in the other_last_rev history, AND 313 # other_last_rev is not in our history, and do it without pulling 314 # history around 315 with self.lock_write(): 316 self.set_bound_location(other.base) 317 318 def get_bound_location(self): 319 try: 320 return self._transport.get_bytes('bound')[:-1].decode('utf-8') 321 except errors.NoSuchFile: 322 return None 323 324 def get_master_branch(self, possible_transports=None): 325 """Return the branch we are bound to. 326 327 :return: Either a Branch, or None 328 """ 329 with self.lock_read(): 330 if self._master_branch_cache is None: 331 self._master_branch_cache = self._get_master_branch( 332 possible_transports) 333 return self._master_branch_cache 334 335 def _get_master_branch(self, possible_transports): 336 bound_loc = self.get_bound_location() 337 if not bound_loc: 338 return None 339 try: 340 return Branch.open(bound_loc, 341 possible_transports=possible_transports) 342 except (errors.NotBranchError, errors.ConnectionError) as e: 343 raise errors.BoundBranchConnectionFailure( 344 self, bound_loc, e) 345 346 def set_bound_location(self, location): 347 """Set the target where this branch is bound to. 348 349 :param location: URL to the target branch 350 """ 351 with self.lock_write(): 352 self._master_branch_cache = None 353 if location: 354 self._transport.put_bytes( 355 'bound', location.encode('utf-8') + b'\n', 356 mode=self.controldir._get_file_mode()) 357 else: 358 try: 359 self._transport.delete('bound') 360 except errors.NoSuchFile: 361 return False 362 return True 363 364 def update(self, possible_transports=None): 365 """Synchronise this branch with the master branch if any. 366 367 :return: None or the last_revision that was pivoted out during the 368 update. 369 """ 370 with self.lock_write(): 371 master = self.get_master_branch(possible_transports) 372 if master is not None: 373 old_tip = _mod_revision.ensure_null(self.last_revision()) 374 self.pull(master, overwrite=True) 375 if self.repository.get_graph().is_ancestor( 376 old_tip, _mod_revision.ensure_null( 377 self.last_revision())): 378 return None 379 return old_tip 380 return None 381 382 def _read_last_revision_info(self): 383 revision_string = self._transport.get_bytes('last-revision') 384 revno, revision_id = revision_string.rstrip(b'\n').split(b' ', 1) 385 revision_id = cache_utf8.get_cached_utf8(revision_id) 386 revno = int(revno) 387 return revno, revision_id 388 389 def _write_last_revision_info(self, revno, revision_id): 390 """Simply write out the revision id, with no checks. 391 392 Use set_last_revision_info to perform this safely. 393 394 Does not update the revision_history cache. 395 """ 396 revision_id = _mod_revision.ensure_null(revision_id) 397 out_string = b'%d %s\n' % (revno, revision_id) 398 self._transport.put_bytes('last-revision', out_string, 399 mode=self.controldir._get_file_mode()) 400 401 def update_feature_flags(self, updated_flags): 402 """Update the feature flags for this branch. 403 404 :param updated_flags: Dictionary mapping feature names to necessities 405 A necessity can be None to indicate the feature should be removed 406 """ 407 with self.lock_write(): 408 self._format._update_feature_flags(updated_flags) 409 self.control_transport.put_bytes( 410 'format', self._format.as_string()) 411 412 def _get_tags_bytes(self): 413 """Get the bytes of a serialised tags dict. 414 415 Note that not all branches support tags, nor do all use the same tags 416 logic: this method is specific to BasicTags. Other tag implementations 417 may use the same method name and behave differently, safely, because 418 of the double-dispatch via 419 format.make_tags->tags_instance->get_tags_dict. 420 421 :return: The bytes of the tags file. 422 :seealso: Branch._set_tags_bytes. 423 """ 424 with self.lock_read(): 425 if self._tags_bytes is None: 426 self._tags_bytes = self._transport.get_bytes('tags') 427 return self._tags_bytes 428 429 def _set_tags_bytes(self, bytes): 430 """Mirror method for _get_tags_bytes. 431 432 :seealso: Branch._get_tags_bytes. 433 """ 434 with self.lock_write(): 435 self._tags_bytes = bytes 436 return self._transport.put_bytes('tags', bytes) 437 438 def _clear_cached_state(self): 439 super(BzrBranch, self)._clear_cached_state() 440 self._tags_bytes = None 441 442 def reconcile(self, thorough=True): 443 """Make sure the data stored in this branch is consistent.""" 444 from .reconcile import BranchReconciler 445 with self.lock_write(): 446 reconciler = BranchReconciler(self, thorough=thorough) 447 return reconciler.reconcile() 448 449 def set_reference_info(self, file_id, branch_location, path=None): 450 """Set the branch location to use for a tree reference.""" 451 raise errors.UnsupportedOperation(self.set_reference_info, self) 452 453 def get_reference_info(self, file_id, path=None): 454 """Get the tree_path and branch_location for a tree reference.""" 455 raise errors.UnsupportedOperation(self.get_reference_info, self) 456 457 def reference_parent(self, file_id, path, possible_transports=None): 458 """Return the parent branch for a tree-reference. 459 460 :param path: The path of the nested tree in the tree 461 :return: A branch associated with the nested tree 462 """ 463 try: 464 branch_location = self.get_reference_info(file_id)[0] 465 except errors.UnsupportedOperation: 466 branch_location = None 467 if branch_location is None: 468 try: 469 return Branch.open_from_transport( 470 self.controldir.root_transport.clone(path), 471 possible_transports=possible_transports) 472 except errors.NotBranchError: 473 return None 474 return Branch.open( 475 urlutils.join( 476 urlutils.strip_segment_parameters(self.user_url), branch_location), 477 possible_transports=possible_transports) 478 479 480class BzrBranch8(BzrBranch): 481 """A branch that stores tree-reference locations.""" 482 483 def _open_hook(self, possible_transports=None): 484 if self._ignore_fallbacks: 485 return 486 if possible_transports is None: 487 possible_transports = [self.controldir.root_transport] 488 try: 489 url = self.get_stacked_on_url() 490 except (errors.UnstackableRepositoryFormat, errors.NotStacked, 491 UnstackableBranchFormat): 492 pass 493 else: 494 for hook in Branch.hooks['transform_fallback_location']: 495 url = hook(self, url) 496 if url is None: 497 hook_name = Branch.hooks.get_hook_name(hook) 498 raise AssertionError( 499 "'transform_fallback_location' hook %s returned " 500 "None, not a URL." % hook_name) 501 self._activate_fallback_location( 502 url, possible_transports=possible_transports) 503 504 def __init__(self, *args, **kwargs): 505 self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False) 506 super(BzrBranch8, self).__init__(*args, **kwargs) 507 self._last_revision_info_cache = None 508 self._reference_info = None 509 510 def _clear_cached_state(self): 511 super(BzrBranch8, self)._clear_cached_state() 512 self._last_revision_info_cache = None 513 self._reference_info = None 514 515 def _check_history_violation(self, revision_id): 516 current_revid = self.last_revision() 517 last_revision = _mod_revision.ensure_null(current_revid) 518 if _mod_revision.is_null(last_revision): 519 return 520 graph = self.repository.get_graph() 521 for lh_ancestor in graph.iter_lefthand_ancestry(revision_id): 522 if lh_ancestor == current_revid: 523 return 524 raise errors.AppendRevisionsOnlyViolation(self.user_url) 525 526 def _gen_revision_history(self): 527 """Generate the revision history from last revision 528 """ 529 last_revno, last_revision = self.last_revision_info() 530 self._extend_partial_history(stop_index=last_revno - 1) 531 return list(reversed(self._partial_revision_history_cache)) 532 533 def _set_parent_location(self, url): 534 """Set the parent branch""" 535 with self.lock_write(): 536 self._set_config_location( 537 'parent_location', url, make_relative=True) 538 539 def _get_parent_location(self): 540 """Set the parent branch""" 541 with self.lock_read(): 542 return self._get_config_location('parent_location') 543 544 def _set_all_reference_info(self, info_dict): 545 """Replace all reference info stored in a branch. 546 547 :param info_dict: A dict of {file_id: (branch_location, tree_path)} 548 """ 549 s = BytesIO() 550 writer = rio.RioWriter(s) 551 for file_id, (branch_location, tree_path) in info_dict.items(): 552 stanza = rio.Stanza(file_id=file_id, 553 branch_location=branch_location) 554 if tree_path is not None: 555 stanza.add('tree_path', tree_path) 556 writer.write_stanza(stanza) 557 with self.lock_write(): 558 self._transport.put_bytes('references', s.getvalue()) 559 self._reference_info = info_dict 560 561 def _get_all_reference_info(self): 562 """Return all the reference info stored in a branch. 563 564 :return: A dict of {tree_path: (branch_location, file_id)} 565 """ 566 with self.lock_read(): 567 if self._reference_info is not None: 568 return self._reference_info 569 try: 570 with self._transport.get('references') as rio_file: 571 stanzas = rio.read_stanzas(rio_file) 572 info_dict = { 573 s['file_id'].encode('utf-8'): ( 574 s['branch_location'], 575 s['tree_path'] if 'tree_path' in s else None) 576 for s in stanzas} 577 except errors.NoSuchFile: 578 info_dict = {} 579 self._reference_info = info_dict 580 return info_dict 581 582 def set_reference_info(self, file_id, branch_location, tree_path=None): 583 """Set the branch location to use for a tree reference. 584 585 :param branch_location: The location of the branch to retrieve tree 586 references from. 587 :param file_id: The file-id of the tree reference. 588 :param tree_path: The path of the tree reference in the tree. 589 """ 590 info_dict = self._get_all_reference_info() 591 info_dict[file_id] = (branch_location, tree_path) 592 if branch_location is None: 593 del info_dict[file_id] 594 self._set_all_reference_info(info_dict) 595 596 def get_reference_info(self, file_id): 597 """Get the tree_path and branch_location for a tree reference. 598 599 :return: a tuple of (branch_location, tree_path) 600 """ 601 return self._get_all_reference_info().get(file_id, (None, None)) 602 603 def set_push_location(self, location): 604 """See Branch.set_push_location.""" 605 self._set_config_location('push_location', location) 606 607 def set_bound_location(self, location): 608 """See Branch.set_push_location.""" 609 self._master_branch_cache = None 610 conf = self.get_config_stack() 611 if location is None: 612 if not conf.get('bound'): 613 return False 614 else: 615 conf.set('bound', 'False') 616 return True 617 else: 618 self._set_config_location('bound_location', location, 619 config=conf) 620 conf.set('bound', 'True') 621 return True 622 623 def _get_bound_location(self, bound): 624 """Return the bound location in the config file. 625 626 Return None if the bound parameter does not match""" 627 conf = self.get_config_stack() 628 if conf.get('bound') != bound: 629 return None 630 return self._get_config_location('bound_location', config=conf) 631 632 def get_bound_location(self): 633 """See Branch.get_bound_location.""" 634 return self._get_bound_location(True) 635 636 def get_old_bound_location(self): 637 """See Branch.get_old_bound_location""" 638 return self._get_bound_location(False) 639 640 def get_stacked_on_url(self): 641 # you can always ask for the URL; but you might not be able to use it 642 # if the repo can't support stacking. 643 # self._check_stackable_repo() 644 # stacked_on_location is only ever defined in branch.conf, so don't 645 # waste effort reading the whole stack of config files. 646 conf = _mod_config.BranchOnlyStack(self) 647 stacked_url = self._get_config_location('stacked_on_location', 648 config=conf) 649 if stacked_url is None: 650 raise errors.NotStacked(self) 651 # TODO(jelmer): Clean this up for pad.lv/1696545 652 return stacked_url 653 654 def get_rev_id(self, revno, history=None): 655 """Find the revision id of the specified revno.""" 656 if revno == 0: 657 return _mod_revision.NULL_REVISION 658 659 with self.lock_read(): 660 last_revno, last_revision_id = self.last_revision_info() 661 if revno <= 0 or revno > last_revno: 662 raise errors.RevnoOutOfBounds(revno, (0, last_revno)) 663 664 if history is not None: 665 return history[revno - 1] 666 667 index = last_revno - revno 668 if len(self._partial_revision_history_cache) <= index: 669 self._extend_partial_history(stop_index=index) 670 if len(self._partial_revision_history_cache) > index: 671 return self._partial_revision_history_cache[index] 672 else: 673 raise errors.NoSuchRevision(self, revno) 674 675 def revision_id_to_revno(self, revision_id): 676 """Given a revision id, return its revno""" 677 if _mod_revision.is_null(revision_id): 678 return 0 679 with self.lock_read(): 680 try: 681 index = self._partial_revision_history_cache.index(revision_id) 682 except ValueError: 683 try: 684 self._extend_partial_history(stop_revision=revision_id) 685 except errors.RevisionNotPresent as e: 686 raise errors.GhostRevisionsHaveNoRevno( 687 revision_id, e.revision_id) 688 index = len(self._partial_revision_history_cache) - 1 689 if index < 0: 690 raise errors.NoSuchRevision(self, revision_id) 691 if self._partial_revision_history_cache[index] != revision_id: 692 raise errors.NoSuchRevision(self, revision_id) 693 return self.revno() - index 694 695 696class BzrBranch7(BzrBranch8): 697 """A branch with support for a fallback repository.""" 698 699 def set_reference_info(self, file_id, branch_location, tree_path=None): 700 super(BzrBranch7, self).set_reference_info( 701 file_id, branch_location, tree_path) 702 format_string = BzrBranchFormat8.get_format_string() 703 mutter('Upgrading branch to format %r', format_string) 704 self._transport.put_bytes('format', format_string) 705 706 707class BzrBranch6(BzrBranch7): 708 """See BzrBranchFormat6 for the capabilities of this branch. 709 710 This subclass of BzrBranch7 disables the new features BzrBranch7 added, 711 i.e. stacking. 712 """ 713 714 def get_stacked_on_url(self): 715 raise UnstackableBranchFormat(self._format, self.user_url) 716 717 718class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat): 719 """Base class for branch formats that live in meta directories. 720 """ 721 722 def __init__(self): 723 BranchFormat.__init__(self) 724 bzrdir.BzrFormat.__init__(self) 725 726 @classmethod 727 def find_format(klass, controldir, name=None): 728 """Return the format for the branch object in controldir.""" 729 try: 730 transport = controldir.get_branch_transport(None, name=name) 731 except errors.NoSuchFile: 732 raise errors.NotBranchError(path=name, controldir=controldir) 733 try: 734 format_string = transport.get_bytes("format") 735 except errors.NoSuchFile: 736 raise errors.NotBranchError( 737 path=transport.base, controldir=controldir) 738 return klass._find_format(format_registry, 'branch', format_string) 739 740 def _branch_class(self): 741 """What class to instantiate on open calls.""" 742 raise NotImplementedError(self._branch_class) 743 744 def _get_initial_config(self, append_revisions_only=None): 745 if append_revisions_only: 746 return b"append_revisions_only = True\n" 747 else: 748 # Avoid writing anything if append_revisions_only is disabled, 749 # as that is the default. 750 return b"" 751 752 def _initialize_helper(self, a_controldir, utf8_files, name=None, 753 repository=None): 754 """Initialize a branch in a control dir, with specified files 755 756 :param a_controldir: The bzrdir to initialize the branch in 757 :param utf8_files: The files to create as a list of 758 (filename, content) tuples 759 :param name: Name of colocated branch to create, if any 760 :return: a branch in this format 761 """ 762 if name is None: 763 name = a_controldir._get_selected_branch() 764 mutter('creating branch %r in %s', self, a_controldir.user_url) 765 branch_transport = a_controldir.get_branch_transport(self, name=name) 766 control_files = lockable_files.LockableFiles(branch_transport, 767 'lock', lockdir.LockDir) 768 control_files.create_lock() 769 control_files.lock_write() 770 try: 771 utf8_files += [('format', self.as_string())] 772 for (filename, content) in utf8_files: 773 branch_transport.put_bytes( 774 filename, content, 775 mode=a_controldir._get_file_mode()) 776 finally: 777 control_files.unlock() 778 branch = self.open(a_controldir, name, _found=True, 779 found_repository=repository) 780 self._run_post_branch_init_hooks(a_controldir, name, branch) 781 return branch 782 783 def open(self, a_controldir, name=None, _found=False, 784 ignore_fallbacks=False, found_repository=None, 785 possible_transports=None): 786 """See BranchFormat.open().""" 787 if name is None: 788 name = a_controldir._get_selected_branch() 789 if not _found: 790 format = BranchFormatMetadir.find_format(a_controldir, name=name) 791 if format.__class__ != self.__class__: 792 raise AssertionError("wrong format %r found for %r" % 793 (format, self)) 794 transport = a_controldir.get_branch_transport(None, name=name) 795 try: 796 control_files = lockable_files.LockableFiles(transport, 'lock', 797 lockdir.LockDir) 798 if found_repository is None: 799 found_repository = a_controldir.find_repository() 800 return self._branch_class()( 801 _format=self, _control_files=control_files, name=name, 802 a_controldir=a_controldir, _repository=found_repository, 803 ignore_fallbacks=ignore_fallbacks, 804 possible_transports=possible_transports) 805 except errors.NoSuchFile: 806 raise errors.NotBranchError( 807 path=transport.base, controldir=a_controldir) 808 809 @property 810 def _matchingcontroldir(self): 811 ret = bzrdir.BzrDirMetaFormat1() 812 ret.set_branch_format(self) 813 return ret 814 815 def supports_tags(self): 816 return True 817 818 def supports_leaving_lock(self): 819 return True 820 821 def check_support_status(self, allow_unsupported, recommend_upgrade=True, 822 basedir=None): 823 BranchFormat.check_support_status( 824 self, allow_unsupported=allow_unsupported, 825 recommend_upgrade=recommend_upgrade, basedir=basedir) 826 bzrdir.BzrFormat.check_support_status( 827 self, allow_unsupported=allow_unsupported, 828 recommend_upgrade=recommend_upgrade, basedir=basedir) 829 830 831class BzrBranchFormat6(BranchFormatMetadir): 832 """Branch format with last-revision and tags. 833 834 Unlike previous formats, this has no explicit revision history. Instead, 835 this just stores the last-revision, and the left-hand history leading 836 up to there is the history. 837 838 This format was introduced in bzr 0.15 839 and became the default in 0.91. 840 """ 841 842 def _branch_class(self): 843 return BzrBranch6 844 845 @classmethod 846 def get_format_string(cls): 847 """See BranchFormat.get_format_string().""" 848 return b"Bazaar Branch Format 6 (bzr 0.15)\n" 849 850 def get_format_description(self): 851 """See BranchFormat.get_format_description().""" 852 return "Branch format 6" 853 854 def initialize(self, a_controldir, name=None, repository=None, 855 append_revisions_only=None): 856 """Create a branch of this format in a_controldir.""" 857 utf8_files = [ 858 ('last-revision', b'0 null:\n'), 859 ('branch.conf', self._get_initial_config(append_revisions_only)), 860 ('tags', b''), 861 ] 862 return self._initialize_helper( 863 a_controldir, utf8_files, name, repository) 864 865 def make_tags(self, branch): 866 """See breezy.branch.BranchFormat.make_tags().""" 867 return _mod_tag.BasicTags(branch) 868 869 def supports_set_append_revisions_only(self): 870 return True 871 872 supports_reference_locations = True 873 874 875class BzrBranchFormat8(BranchFormatMetadir): 876 """Metadir format supporting storing locations of subtree branches.""" 877 878 def _branch_class(self): 879 return BzrBranch8 880 881 @classmethod 882 def get_format_string(cls): 883 """See BranchFormat.get_format_string().""" 884 return b"Bazaar Branch Format 8 (needs bzr 1.15)\n" 885 886 def get_format_description(self): 887 """See BranchFormat.get_format_description().""" 888 return "Branch format 8" 889 890 def initialize(self, a_controldir, name=None, repository=None, 891 append_revisions_only=None): 892 """Create a branch of this format in a_controldir.""" 893 utf8_files = [('last-revision', b'0 null:\n'), 894 ('branch.conf', 895 self._get_initial_config(append_revisions_only)), 896 ('tags', b''), 897 ('references', b'') 898 ] 899 return self._initialize_helper( 900 a_controldir, utf8_files, name, repository) 901 902 def make_tags(self, branch): 903 """See breezy.branch.BranchFormat.make_tags().""" 904 return _mod_tag.BasicTags(branch) 905 906 def supports_set_append_revisions_only(self): 907 return True 908 909 def supports_stacking(self): 910 return True 911 912 supports_reference_locations = True 913 914 915class BzrBranchFormat7(BranchFormatMetadir): 916 """Branch format with last-revision, tags, and a stacked location pointer. 917 918 The stacked location pointer is passed down to the repository and requires 919 a repository format with supports_external_lookups = True. 920 921 This format was introduced in bzr 1.6. 922 """ 923 924 def initialize(self, a_controldir, name=None, repository=None, 925 append_revisions_only=None): 926 """Create a branch of this format in a_controldir.""" 927 utf8_files = [('last-revision', b'0 null:\n'), 928 ('branch.conf', 929 self._get_initial_config(append_revisions_only)), 930 ('tags', b''), 931 ] 932 return self._initialize_helper( 933 a_controldir, utf8_files, name, repository) 934 935 def _branch_class(self): 936 return BzrBranch7 937 938 @classmethod 939 def get_format_string(cls): 940 """See BranchFormat.get_format_string().""" 941 return b"Bazaar Branch Format 7 (needs bzr 1.6)\n" 942 943 def get_format_description(self): 944 """See BranchFormat.get_format_description().""" 945 return "Branch format 7" 946 947 def supports_set_append_revisions_only(self): 948 return True 949 950 def supports_stacking(self): 951 return True 952 953 def make_tags(self, branch): 954 """See breezy.branch.BranchFormat.make_tags().""" 955 return _mod_tag.BasicTags(branch) 956 957 # This is a white lie; as soon as you set a reference location, we upgrade 958 # you to BzrBranchFormat8. 959 supports_reference_locations = True 960 961 962class BranchReferenceFormat(BranchFormatMetadir): 963 """Bzr branch reference format. 964 965 Branch references are used in implementing checkouts, they 966 act as an alias to the real branch which is at some other url. 967 968 This format has: 969 - A location file 970 - a format string 971 """ 972 973 @classmethod 974 def get_format_string(cls): 975 """See BranchFormat.get_format_string().""" 976 return b"Bazaar-NG Branch Reference Format 1\n" 977 978 def get_format_description(self): 979 """See BranchFormat.get_format_description().""" 980 return "Checkout reference format 1" 981 982 def get_reference(self, a_controldir, name=None): 983 """See BranchFormat.get_reference().""" 984 transport = a_controldir.get_branch_transport(None, name=name) 985 url = urlutils.strip_segment_parameters(a_controldir.user_url) 986 return urlutils.join( 987 url, transport.get_bytes('location').decode('utf-8')) 988 989 def _write_reference(self, a_controldir, transport, to_branch): 990 to_url = to_branch.user_url 991 # Ideally, we'd write a relative path here for the benefit of colocated 992 # branches - so that moving a control directory doesn't break 993 # any references to colocated branches. Unfortunately, bzr 994 # does not support relative URLs. See pad.lv/1803845 -- jelmer 995 # to_url = urlutils.relative_url( 996 # a_controldir.user_url, to_branch.user_url) 997 transport.put_bytes('location', to_url.encode('utf-8')) 998 999 def set_reference(self, a_controldir, name, to_branch): 1000 """See BranchFormat.set_reference().""" 1001 transport = a_controldir.get_branch_transport(None, name=name) 1002 self._write_reference(a_controldir, transport, to_branch) 1003 1004 def initialize(self, a_controldir, name=None, target_branch=None, 1005 repository=None, append_revisions_only=None): 1006 """Create a branch of this format in a_controldir.""" 1007 if target_branch is None: 1008 # this format does not implement branch itself, thus the implicit 1009 # creation contract must see it as uninitializable 1010 raise errors.UninitializableFormat(self) 1011 mutter('creating branch reference in %s', a_controldir.user_url) 1012 if a_controldir._format.fixed_components: 1013 raise errors.IncompatibleFormat(self, a_controldir._format) 1014 if name is None: 1015 name = a_controldir._get_selected_branch() 1016 branch_transport = a_controldir.get_branch_transport(self, name=name) 1017 self._write_reference(a_controldir, branch_transport, target_branch) 1018 branch_transport.put_bytes('format', self.as_string()) 1019 branch = self.open(a_controldir, name, _found=True, 1020 possible_transports=[target_branch.controldir.root_transport]) 1021 self._run_post_branch_init_hooks(a_controldir, name, branch) 1022 return branch 1023 1024 def _make_reference_clone_function(format, a_branch): 1025 """Create a clone() routine for a branch dynamically.""" 1026 def clone(to_bzrdir, revision_id=None, repository_policy=None, name=None, 1027 tag_selector=None): 1028 """See Branch.clone().""" 1029 return format.initialize(to_bzrdir, target_branch=a_branch, name=name) 1030 # cannot obey revision_id limits when cloning a reference ... 1031 # FIXME RBC 20060210 either nuke revision_id for clone, or 1032 # emit some sort of warning/error to the caller ?! 1033 return clone 1034 1035 def open(self, a_controldir, name=None, _found=False, location=None, 1036 possible_transports=None, ignore_fallbacks=False, 1037 found_repository=None): 1038 """Return the branch that the branch reference in a_controldir points at. 1039 1040 :param a_controldir: A BzrDir that contains a branch. 1041 :param name: Name of colocated branch to open, if any 1042 :param _found: a private parameter, do not use it. It is used to 1043 indicate if format probing has already be done. 1044 :param ignore_fallbacks: when set, no fallback branches will be opened 1045 (if there are any). Default is to open fallbacks. 1046 :param location: The location of the referenced branch. If 1047 unspecified, this will be determined from the branch reference in 1048 a_controldir. 1049 :param possible_transports: An optional reusable transports list. 1050 """ 1051 if name is None: 1052 name = a_controldir._get_selected_branch() 1053 if not _found: 1054 format = BranchFormatMetadir.find_format(a_controldir, name=name) 1055 if format.__class__ != self.__class__: 1056 raise AssertionError("wrong format %r found for %r" % 1057 (format, self)) 1058 if location is None: 1059 location = self.get_reference(a_controldir, name) 1060 real_bzrdir = controldir.ControlDir.open( 1061 location, possible_transports=possible_transports) 1062 result = real_bzrdir.open_branch( 1063 ignore_fallbacks=ignore_fallbacks, 1064 possible_transports=possible_transports) 1065 # this changes the behaviour of result.clone to create a new reference 1066 # rather than a copy of the content of the branch. 1067 # I did not use a proxy object because that needs much more extensive 1068 # testing, and we are only changing one behaviour at the moment. 1069 # If we decide to alter more behaviours - i.e. the implicit nickname 1070 # then this should be refactored to introduce a tested proxy branch 1071 # and a subclass of that for use in overriding clone() and .... 1072 # - RBC 20060210 1073 result.clone = self._make_reference_clone_function(result) 1074 return result 1075 1076 1077class Converter5to6(object): 1078 """Perform an in-place upgrade of format 5 to format 6""" 1079 1080 def convert(self, branch): 1081 # Data for 5 and 6 can peacefully coexist. 1082 format = BzrBranchFormat6() 1083 new_branch = format.open(branch.controldir, _found=True) 1084 1085 # Copy source data into target 1086 new_branch._write_last_revision_info(*branch.last_revision_info()) 1087 with new_branch.lock_write(): 1088 new_branch.set_parent(branch.get_parent()) 1089 new_branch.set_bound_location(branch.get_bound_location()) 1090 new_branch.set_push_location(branch.get_push_location()) 1091 1092 # New branch has no tags by default 1093 new_branch.tags._set_tag_dict({}) 1094 1095 # Copying done; now update target format 1096 new_branch._transport.put_bytes( 1097 'format', format.as_string(), 1098 mode=new_branch.controldir._get_file_mode()) 1099 1100 # Clean up old files 1101 new_branch._transport.delete('revision-history') 1102 with branch.lock_write(): 1103 try: 1104 branch.set_parent(None) 1105 except errors.NoSuchFile: 1106 pass 1107 branch.set_bound_location(None) 1108 1109 1110class Converter6to7(object): 1111 """Perform an in-place upgrade of format 6 to format 7""" 1112 1113 def convert(self, branch): 1114 format = BzrBranchFormat7() 1115 branch._set_config_location('stacked_on_location', '') 1116 # update target format 1117 branch._transport.put_bytes('format', format.as_string()) 1118 1119 1120class Converter7to8(object): 1121 """Perform an in-place upgrade of format 7 to format 8""" 1122 1123 def convert(self, branch): 1124 format = BzrBranchFormat8() 1125 branch._transport.put_bytes('references', b'') 1126 # update target format 1127 branch._transport.put_bytes('format', format.as_string()) 1128