1# Copyright (C) 2006-2010 Canonical Ltd 2# 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2 of the License, or 6# (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software 15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 17"""Weave-era BzrDir formats.""" 18 19from io import BytesIO 20 21from ...bzr.bzrdir import ( 22 BzrDir, 23 BzrDirFormat, 24 BzrDirMetaFormat1, 25 ) 26from ...controldir import ( 27 ControlDir, 28 Converter, 29 MustHaveWorkingTree, 30 format_registry, 31 ) 32from ... import ( 33 errors, 34 ) 35from ...lazy_import import lazy_import 36lazy_import(globals(), """ 37import os 38import warnings 39 40from breezy import ( 41 branch as _mod_branch,, 42 graph, 43 lockable_files, 44 lockdir, 45 osutils, 46 revision as _mod_revision, 47 trace, 48 ui, 49 urlutils, 50 ) 51from breezy.bzr import ( 52 versionedfile, 53 weave, 54 xml5, 55 ) 56from breezy.i18n import gettext 57from breezy.plugins.weave_fmt.store.versioned import VersionedFileStore 58from breezy.transactions import WriteTransaction 59from breezy.transport import ( 60 get_transport, 61 local, 62 ) 63from breezy.plugins.weave_fmt import xml4 64""") 65 66 67class BzrDirFormatAllInOne(BzrDirFormat): 68 """Common class for formats before meta-dirs.""" 69 70 fixed_components = True 71 72 def initialize_on_transport_ex(self, transport, use_existing_dir=False, 73 create_prefix=False, force_new_repo=False, stacked_on=None, 74 stack_on_pwd=None, repo_format_name=None, make_working_trees=None, 75 shared_repo=False): 76 """See ControlDir.initialize_on_transport_ex.""" 77 require_stacking = (stacked_on is not None) 78 # Format 5 cannot stack, but we've been asked to - actually init 79 # a Meta1Dir 80 if require_stacking: 81 format = BzrDirMetaFormat1() 82 return format.initialize_on_transport_ex(transport, 83 use_existing_dir=use_existing_dir, create_prefix=create_prefix, 84 force_new_repo=force_new_repo, stacked_on=stacked_on, 85 stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name, 86 make_working_trees=make_working_trees, shared_repo=shared_repo) 87 return BzrDirFormat.initialize_on_transport_ex(self, transport, 88 use_existing_dir=use_existing_dir, create_prefix=create_prefix, 89 force_new_repo=force_new_repo, stacked_on=stacked_on, 90 stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name, 91 make_working_trees=make_working_trees, shared_repo=shared_repo) 92 93 @classmethod 94 def from_string(cls, format_string): 95 if format_string != cls.get_format_string(): 96 raise AssertionError("unexpected format string %r" % format_string) 97 return cls() 98 99 100class BzrDirFormat5(BzrDirFormatAllInOne): 101 """Bzr control format 5. 102 103 This format is a combined format for working tree, branch and repository. 104 It has: 105 - Format 2 working trees [always] 106 - Format 4 branches [always] 107 - Format 5 repositories [always] 108 Unhashed stores in the repository. 109 """ 110 111 _lock_class = lockable_files.TransportLock 112 113 def __eq__(self, other): 114 return isinstance(self, type(other)) 115 116 @classmethod 117 def get_format_string(cls): 118 """See BzrDirFormat.get_format_string().""" 119 return b"Bazaar-NG branch, format 5\n" 120 121 def get_branch_format(self): 122 from .branch import BzrBranchFormat4 123 return BzrBranchFormat4() 124 125 def get_format_description(self): 126 """See ControlDirFormat.get_format_description().""" 127 return "All-in-one format 5" 128 129 def get_converter(self, format=None): 130 """See ControlDirFormat.get_converter().""" 131 # there is one and only one upgrade path here. 132 return ConvertBzrDir5To6() 133 134 def _initialize_for_clone(self, url): 135 return self.initialize_on_transport(get_transport(url), _cloning=True) 136 137 def initialize_on_transport(self, transport, _cloning=False): 138 """Format 5 dirs always have working tree, branch and repository. 139 140 Except when they are being cloned. 141 """ 142 from .branch import BzrBranchFormat4 143 from .repository import RepositoryFormat5 144 result = (super(BzrDirFormat5, self).initialize_on_transport(transport)) 145 RepositoryFormat5().initialize(result, _internal=True) 146 if not _cloning: 147 branch = BzrBranchFormat4().initialize(result) 148 result._init_workingtree() 149 return result 150 151 def network_name(self): 152 return self.get_format_string() 153 154 def _open(self, transport): 155 """See BzrDirFormat._open.""" 156 return BzrDir5(transport, self) 157 158 def __return_repository_format(self): 159 """Circular import protection.""" 160 from .repository import RepositoryFormat5 161 return RepositoryFormat5() 162 repository_format = property(__return_repository_format) 163 164 165class BzrDirFormat6(BzrDirFormatAllInOne): 166 """Bzr control format 6. 167 168 This format is a combined format for working tree, branch and repository. 169 It has: 170 - Format 2 working trees [always] 171 - Format 4 branches [always] 172 - Format 6 repositories [always] 173 """ 174 175 _lock_class = lockable_files.TransportLock 176 177 def __eq__(self, other): 178 return isinstance(self, type(other)) 179 180 @classmethod 181 def get_format_string(cls): 182 """See BzrDirFormat.get_format_string().""" 183 return b"Bazaar-NG branch, format 6\n" 184 185 def get_format_description(self): 186 """See ControlDirFormat.get_format_description().""" 187 return "All-in-one format 6" 188 189 def get_branch_format(self): 190 from .branch import BzrBranchFormat4 191 return BzrBranchFormat4() 192 193 def get_converter(self, format=None): 194 """See ControlDirFormat.get_converter().""" 195 # there is one and only one upgrade path here. 196 return ConvertBzrDir6ToMeta() 197 198 def _initialize_for_clone(self, url): 199 return self.initialize_on_transport(get_transport(url), _cloning=True) 200 201 def initialize_on_transport(self, transport, _cloning=False): 202 """Format 6 dirs always have working tree, branch and repository. 203 204 Except when they are being cloned. 205 """ 206 from .branch import BzrBranchFormat4 207 from .repository import RepositoryFormat6 208 result = super(BzrDirFormat6, self).initialize_on_transport(transport) 209 RepositoryFormat6().initialize(result, _internal=True) 210 if not _cloning: 211 branch = BzrBranchFormat4().initialize(result) 212 result._init_workingtree() 213 return result 214 215 def network_name(self): 216 return self.get_format_string() 217 218 def _open(self, transport): 219 """See BzrDirFormat._open.""" 220 return BzrDir6(transport, self) 221 222 def __return_repository_format(self): 223 """Circular import protection.""" 224 from .repository import RepositoryFormat6 225 return RepositoryFormat6() 226 repository_format = property(__return_repository_format) 227 228 229class ConvertBzrDir4To5(Converter): 230 """Converts format 4 bzr dirs to format 5.""" 231 232 def __init__(self): 233 super(ConvertBzrDir4To5, self).__init__() 234 self.converted_revs = set() 235 self.absent_revisions = set() 236 self.text_count = 0 237 self.revisions = {} 238 239 def convert(self, to_convert, pb): 240 """See Converter.convert().""" 241 self.controldir = to_convert 242 if pb is not None: 243 warnings.warn(gettext("pb parameter to convert() is deprecated")) 244 with ui.ui_factory.nested_progress_bar() as self.pb: 245 ui.ui_factory.note(gettext('starting upgrade from format 4 to 5')) 246 if isinstance(self.controldir.transport, local.LocalTransport): 247 self.controldir.get_workingtree_transport( 248 None).delete('stat-cache') 249 self._convert_to_weaves() 250 return ControlDir.open(self.controldir.user_url) 251 252 def _convert_to_weaves(self): 253 ui.ui_factory.note(gettext( 254 'note: upgrade may be faster if all store files are ungzipped first')) 255 try: 256 # TODO permissions 257 stat = self.controldir.transport.stat('weaves') 258 if not S_ISDIR(stat.st_mode): 259 self.controldir.transport.delete('weaves') 260 self.controldir.transport.mkdir('weaves') 261 except errors.NoSuchFile: 262 self.controldir.transport.mkdir('weaves') 263 # deliberately not a WeaveFile as we want to build it up slowly. 264 self.inv_weave = weave.Weave('inventory') 265 # holds in-memory weaves for all files 266 self.text_weaves = {} 267 self.controldir.transport.delete('branch-format') 268 self.branch = self.controldir.open_branch() 269 self._convert_working_inv() 270 rev_history = self.branch._revision_history() 271 # to_read is a stack holding the revisions we still need to process; 272 # appending to it adds new highest-priority revisions 273 self.known_revisions = set(rev_history) 274 self.to_read = rev_history[-1:] 275 while self.to_read: 276 rev_id = self.to_read.pop() 277 if (rev_id not in self.revisions and 278 rev_id not in self.absent_revisions): 279 self._load_one_rev(rev_id) 280 self.pb.clear() 281 to_import = self._make_order() 282 for i, rev_id in enumerate(to_import): 283 self.pb.update(gettext('converting revision'), i, len(to_import)) 284 self._convert_one_rev(rev_id) 285 self.pb.clear() 286 self._write_all_weaves() 287 self._write_all_revs() 288 ui.ui_factory.note(gettext('upgraded to weaves:')) 289 ui.ui_factory.note(' ' + gettext('%6d revisions and inventories') % 290 len(self.revisions)) 291 ui.ui_factory.note(' ' + gettext('%6d revisions not present') % 292 len(self.absent_revisions)) 293 ui.ui_factory.note(' ' + gettext('%6d texts') % self.text_count) 294 self._cleanup_spare_files_after_format4() 295 self.branch._transport.put_bytes( 296 'branch-format', 297 BzrDirFormat5().get_format_string(), 298 mode=self.controldir._get_file_mode()) 299 300 def _cleanup_spare_files_after_format4(self): 301 # FIXME working tree upgrade foo. 302 for n in 'merged-patches', 'pending-merged-patches': 303 try: 304 ## assert os.path.getsize(p) == 0 305 self.controldir.transport.delete(n) 306 except errors.NoSuchFile: 307 pass 308 self.controldir.transport.delete_tree('inventory-store') 309 self.controldir.transport.delete_tree('text-store') 310 311 def _convert_working_inv(self): 312 inv = xml4.serializer_v4.read_inventory( 313 self.branch._transport.get('inventory')) 314 f = BytesIO() 315 xml5.serializer_v5.write_inventory(inv, f, working=True) 316 self.branch._transport.put_bytes('inventory', f.getvalue(), 317 mode=self.controldir._get_file_mode()) 318 319 def _write_all_weaves(self): 320 controlweaves = VersionedFileStore(self.controldir.transport, prefixed=False, 321 versionedfile_class=weave.WeaveFile) 322 weave_transport = self.controldir.transport.clone('weaves') 323 weaves = VersionedFileStore(weave_transport, prefixed=False, 324 versionedfile_class=weave.WeaveFile) 325 transaction = WriteTransaction() 326 327 try: 328 i = 0 329 for file_id, file_weave in self.text_weaves.items(): 330 self.pb.update(gettext('writing weave'), i, 331 len(self.text_weaves)) 332 weaves._put_weave(file_id, file_weave, transaction) 333 i += 1 334 self.pb.update(gettext('inventory'), 0, 1) 335 controlweaves._put_weave(b'inventory', self.inv_weave, transaction) 336 self.pb.update(gettext('inventory'), 1, 1) 337 finally: 338 self.pb.clear() 339 340 def _write_all_revs(self): 341 """Write all revisions out in new form.""" 342 self.controldir.transport.delete_tree('revision-store') 343 self.controldir.transport.mkdir('revision-store') 344 revision_transport = self.controldir.transport.clone('revision-store') 345 # TODO permissions 346 from ...bzr.xml5 import serializer_v5 347 from .repository import RevisionTextStore 348 revision_store = RevisionTextStore(revision_transport, 349 serializer_v5, False, versionedfile.PrefixMapper(), 350 lambda: True, lambda: True) 351 try: 352 for i, rev_id in enumerate(self.converted_revs): 353 self.pb.update(gettext('write revision'), i, 354 len(self.converted_revs)) 355 lines = serializer_v5.write_revision_to_lines( 356 self.revisions[rev_id]) 357 key = (rev_id,) 358 revision_store.add_lines(key, None, lines) 359 finally: 360 self.pb.clear() 361 362 def _load_one_rev(self, rev_id): 363 """Load a revision object into memory. 364 365 Any parents not either loaded or abandoned get queued to be 366 loaded.""" 367 self.pb.update(gettext('loading revision'), 368 len(self.revisions), 369 len(self.known_revisions)) 370 if not self.branch.repository.has_revision(rev_id): 371 self.pb.clear() 372 ui.ui_factory.note(gettext('revision {%s} not present in branch; ' 373 'will be converted as a ghost') % 374 rev_id) 375 self.absent_revisions.add(rev_id) 376 else: 377 rev = self.branch.repository.get_revision(rev_id) 378 for parent_id in rev.parent_ids: 379 self.known_revisions.add(parent_id) 380 self.to_read.append(parent_id) 381 self.revisions[rev_id] = rev 382 383 def _load_old_inventory(self, rev_id): 384 with self.branch.repository.inventory_store.get(rev_id) as f: 385 inv = xml4.serializer_v4.read_inventory(f) 386 inv.revision_id = rev_id 387 rev = self.revisions[rev_id] 388 return inv 389 390 def _load_updated_inventory(self, rev_id): 391 inv_xml = self.inv_weave.get_lines(rev_id) 392 inv = xml5.serializer_v5.read_inventory_from_lines(inv_xml, rev_id) 393 return inv 394 395 def _convert_one_rev(self, rev_id): 396 """Convert revision and all referenced objects to new format.""" 397 rev = self.revisions[rev_id] 398 inv = self._load_old_inventory(rev_id) 399 present_parents = [p for p in rev.parent_ids 400 if p not in self.absent_revisions] 401 self._convert_revision_contents(rev, inv, present_parents) 402 self._store_new_inv(rev, inv, present_parents) 403 self.converted_revs.add(rev_id) 404 405 def _store_new_inv(self, rev, inv, present_parents): 406 new_inv_xml = xml5.serializer_v5.write_inventory_to_lines(inv) 407 new_inv_sha1 = osutils.sha_strings(new_inv_xml) 408 self.inv_weave.add_lines(rev.revision_id, 409 present_parents, 410 new_inv_xml) 411 rev.inventory_sha1 = new_inv_sha1 412 413 def _convert_revision_contents(self, rev, inv, present_parents): 414 """Convert all the files within a revision. 415 416 Also upgrade the inventory to refer to the text revision ids.""" 417 rev_id = rev.revision_id 418 trace.mutter('converting texts of revision {%s}', rev_id) 419 parent_invs = list(map(self._load_updated_inventory, present_parents)) 420 entries = inv.iter_entries() 421 next(entries) 422 for path, ie in entries: 423 self._convert_file_version(rev, ie, parent_invs) 424 425 def _convert_file_version(self, rev, ie, parent_invs): 426 """Convert one version of one file. 427 428 The file needs to be added into the weave if it is a merge 429 of >=2 parents or if it's changed from its parent. 430 """ 431 file_id = ie.file_id 432 rev_id = rev.revision_id 433 w = self.text_weaves.get(file_id) 434 if w is None: 435 w = weave.Weave(file_id) 436 self.text_weaves[file_id] = w 437 text_changed = False 438 parent_candiate_entries = ie.parent_candidates(parent_invs) 439 heads = graph.Graph(self).heads(parent_candiate_entries) 440 # XXX: Note that this is unordered - and this is tolerable because 441 # the previous code was also unordered. 442 previous_entries = {head: parent_candiate_entries[head] 443 for head in heads} 444 self.snapshot_ie(previous_entries, ie, w, rev_id) 445 446 def get_parent_map(self, revision_ids): 447 """See graph.StackedParentsProvider.get_parent_map""" 448 return dict((revision_id, self.revisions[revision_id]) 449 for revision_id in revision_ids 450 if revision_id in self.revisions) 451 452 def snapshot_ie(self, previous_revisions, ie, w, rev_id): 453 # TODO: convert this logic, which is ~= snapshot to 454 # a call to:. This needs the path figured out. rather than a work_tree 455 # a v4 revision_tree can be given, or something that looks enough like 456 # one to give the file content to the entry if it needs it. 457 # and we need something that looks like a weave store for snapshot to 458 # save against. 459 #ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves)) 460 if len(previous_revisions) == 1: 461 previous_ie = next(iter(previous_revisions.values())) 462 if ie._unchanged(previous_ie): 463 ie.revision = previous_ie.revision 464 return 465 if ie.has_text(): 466 with self.branch.repository._text_store.get(ie.text_id) as f: 467 file_lines = f.readlines() 468 w.add_lines(rev_id, previous_revisions, file_lines) 469 self.text_count += 1 470 else: 471 w.add_lines(rev_id, previous_revisions, []) 472 ie.revision = rev_id 473 474 def _make_order(self): 475 """Return a suitable order for importing revisions. 476 477 The order must be such that an revision is imported after all 478 its (present) parents. 479 """ 480 todo = set(self.revisions) 481 done = self.absent_revisions.copy() 482 order = [] 483 while todo: 484 # scan through looking for a revision whose parents 485 # are all done 486 for rev_id in sorted(list(todo)): 487 rev = self.revisions[rev_id] 488 parent_ids = set(rev.parent_ids) 489 if parent_ids.issubset(done): 490 # can take this one now 491 order.append(rev_id) 492 todo.remove(rev_id) 493 done.add(rev_id) 494 return order 495 496 497class ConvertBzrDir5To6(Converter): 498 """Converts format 5 bzr dirs to format 6.""" 499 500 def convert(self, to_convert, pb): 501 """See Converter.convert().""" 502 self.controldir = to_convert 503 with ui.ui_factory.nested_progress_bar() as pb: 504 ui.ui_factory.note(gettext('starting upgrade from format 5 to 6')) 505 self._convert_to_prefixed() 506 return ControlDir.open(self.controldir.user_url) 507 508 def _convert_to_prefixed(self): 509 from .store import TransportStore 510 self.controldir.transport.delete('branch-format') 511 for store_name in ["weaves", "revision-store"]: 512 ui.ui_factory.note(gettext("adding prefixes to %s") % store_name) 513 store_transport = self.controldir.transport.clone(store_name) 514 store = TransportStore(store_transport, prefixed=True) 515 for urlfilename in store_transport.list_dir('.'): 516 filename = urlutils.unescape(urlfilename) 517 if (filename.endswith(".weave") 518 or filename.endswith(".gz") 519 or filename.endswith(".sig")): 520 file_id, suffix = os.path.splitext(filename) 521 else: 522 file_id = filename 523 suffix = '' 524 new_name = store._mapper.map( 525 (file_id.encode('utf-8'),)) + suffix 526 # FIXME keep track of the dirs made RBC 20060121 527 try: 528 store_transport.move(filename, new_name) 529 except errors.NoSuchFile: # catches missing dirs strangely enough 530 store_transport.mkdir(osutils.dirname(new_name)) 531 store_transport.move(filename, new_name) 532 self.controldir.transport.put_bytes( 533 'branch-format', 534 BzrDirFormat6().get_format_string(), 535 mode=self.controldir._get_file_mode()) 536 537 538class ConvertBzrDir6ToMeta(Converter): 539 """Converts format 6 bzr dirs to metadirs.""" 540 541 def convert(self, to_convert, pb): 542 """See Converter.convert().""" 543 from .repository import RepositoryFormat7 544 from ...bzr.fullhistory import BzrBranchFormat5 545 self.controldir = to_convert 546 self.pb = ui.ui_factory.nested_progress_bar() 547 self.count = 0 548 self.total = 20 # the steps we know about 549 self.garbage_inventories = [] 550 self.dir_mode = self.controldir._get_dir_mode() 551 self.file_mode = self.controldir._get_file_mode() 552 553 ui.ui_factory.note( 554 gettext('starting upgrade from format 6 to metadir')) 555 self.controldir.transport.put_bytes( 556 'branch-format', 557 b"Converting to format 6", 558 mode=self.file_mode) 559 # its faster to move specific files around than to open and use the apis... 560 # first off, nuke ancestry.weave, it was never used. 561 try: 562 self.step(gettext('Removing ancestry.weave')) 563 self.controldir.transport.delete('ancestry.weave') 564 except errors.NoSuchFile: 565 pass 566 # find out whats there 567 self.step(gettext('Finding branch files')) 568 last_revision = self.controldir.open_branch().last_revision() 569 bzrcontents = self.controldir.transport.list_dir('.') 570 for name in bzrcontents: 571 if name.startswith('basis-inventory.'): 572 self.garbage_inventories.append(name) 573 # create new directories for repository, working tree and branch 574 repository_names = [('inventory.weave', True), 575 ('revision-store', True), 576 ('weaves', True)] 577 self.step(gettext('Upgrading repository') + ' ') 578 self.controldir.transport.mkdir('repository', mode=self.dir_mode) 579 self.make_lock('repository') 580 # we hard code the formats here because we are converting into 581 # the meta format. The meta format upgrader can take this to a 582 # future format within each component. 583 self.put_format('repository', RepositoryFormat7()) 584 for entry in repository_names: 585 self.move_entry('repository', entry) 586 587 self.step(gettext('Upgrading branch') + ' ') 588 self.controldir.transport.mkdir('branch', mode=self.dir_mode) 589 self.make_lock('branch') 590 self.put_format('branch', BzrBranchFormat5()) 591 branch_files = [('revision-history', True), 592 ('branch-name', True), 593 ('parent', False)] 594 for entry in branch_files: 595 self.move_entry('branch', entry) 596 597 checkout_files = [('pending-merges', True), 598 ('inventory', True), 599 ('stat-cache', False)] 600 # If a mandatory checkout file is not present, the branch does not have 601 # a functional checkout. Do not create a checkout in the converted 602 # branch. 603 for name, mandatory in checkout_files: 604 if mandatory and name not in bzrcontents: 605 has_checkout = False 606 break 607 else: 608 has_checkout = True 609 if not has_checkout: 610 ui.ui_factory.note(gettext('No working tree.')) 611 # If some checkout files are there, we may as well get rid of them. 612 for name, mandatory in checkout_files: 613 if name in bzrcontents: 614 self.controldir.transport.delete(name) 615 else: 616 from ...bzr.workingtree_3 import WorkingTreeFormat3 617 self.step(gettext('Upgrading working tree')) 618 self.controldir.transport.mkdir('checkout', mode=self.dir_mode) 619 self.make_lock('checkout') 620 self.put_format('checkout', WorkingTreeFormat3()) 621 for path in self.garbage_inventories: 622 self.controldir.transport.delete(path) 623 for entry in checkout_files: 624 self.move_entry('checkout', entry) 625 if last_revision is not None: 626 self.controldir.transport.put_bytes( 627 'checkout/last-revision', last_revision) 628 self.controldir.transport.put_bytes( 629 'branch-format', 630 BzrDirMetaFormat1().get_format_string(), 631 mode=self.file_mode) 632 self.pb.finished() 633 return ControlDir.open(self.controldir.user_url) 634 635 def make_lock(self, name): 636 """Make a lock for the new control dir name.""" 637 self.step(gettext('Make %s lock') % name) 638 ld = lockdir.LockDir(self.controldir.transport, 639 '%s/lock' % name, 640 file_modebits=self.file_mode, 641 dir_modebits=self.dir_mode) 642 ld.create() 643 644 def move_entry(self, new_dir, entry): 645 """Move then entry name into new_dir.""" 646 name = entry[0] 647 mandatory = entry[1] 648 self.step(gettext('Moving %s') % name) 649 try: 650 self.controldir.transport.move(name, '%s/%s' % (new_dir, name)) 651 except errors.NoSuchFile: 652 if mandatory: 653 raise 654 655 def put_format(self, dirname, format): 656 self.controldir.transport.put_bytes('%s/format' % dirname, 657 format.get_format_string(), 658 self.file_mode) 659 660 661class BzrDirFormat4(BzrDirFormat): 662 """Bzr dir format 4. 663 664 This format is a combined format for working tree, branch and repository. 665 It has: 666 - Format 1 working trees [always] 667 - Format 4 branches [always] 668 - Format 4 repositories [always] 669 670 This format is deprecated: it indexes texts using a text it which is 671 removed in format 5; write support for this format has been removed. 672 """ 673 674 _lock_class = lockable_files.TransportLock 675 676 def __eq__(self, other): 677 return isinstance(self, type(other)) 678 679 @classmethod 680 def get_format_string(cls): 681 """See BzrDirFormat.get_format_string().""" 682 return b"Bazaar-NG branch, format 0.0.4\n" 683 684 def get_format_description(self): 685 """See ControlDirFormat.get_format_description().""" 686 return "All-in-one format 4" 687 688 def get_converter(self, format=None): 689 """See ControlDirFormat.get_converter().""" 690 # there is one and only one upgrade path here. 691 return ConvertBzrDir4To5() 692 693 def initialize_on_transport(self, transport): 694 """Format 4 branches cannot be created.""" 695 raise errors.UninitializableFormat(self) 696 697 def is_supported(self): 698 """Format 4 is not supported. 699 700 It is not supported because the model changed from 4 to 5 and the 701 conversion logic is expensive - so doing it on the fly was not 702 feasible. 703 """ 704 return False 705 706 def network_name(self): 707 return self.get_format_string() 708 709 def _open(self, transport): 710 """See BzrDirFormat._open.""" 711 return BzrDir4(transport, self) 712 713 def __return_repository_format(self): 714 """Circular import protection.""" 715 from .repository import RepositoryFormat4 716 return RepositoryFormat4() 717 repository_format = property(__return_repository_format) 718 719 @classmethod 720 def from_string(cls, format_string): 721 if format_string != cls.get_format_string(): 722 raise AssertionError("unexpected format string %r" % format_string) 723 return cls() 724 725 726class BzrDirPreSplitOut(BzrDir): 727 """A common class for the all-in-one formats.""" 728 729 def __init__(self, _transport, _format): 730 """See ControlDir.__init__.""" 731 super(BzrDirPreSplitOut, self).__init__(_transport, _format) 732 self._control_files = lockable_files.LockableFiles( 733 self.get_branch_transport(None), 734 self._format._lock_file_name, 735 self._format._lock_class) 736 737 def break_lock(self): 738 """Pre-splitout bzrdirs do not suffer from stale locks.""" 739 raise NotImplementedError(self.break_lock) 740 741 def cloning_metadir(self, require_stacking=False): 742 """Produce a metadir suitable for cloning with.""" 743 if require_stacking: 744 return format_registry.make_controldir('1.6') 745 return self._format.__class__() 746 747 def clone(self, url, revision_id=None, force_new_repo=False, 748 preserve_stacking=False, tag_selector=None): 749 """See ControlDir.clone(). 750 751 force_new_repo has no effect, since this family of formats always 752 require a new repository. 753 preserve_stacking has no effect, since no source branch using this 754 family of formats can be stacked, so there is no stacking to preserve. 755 """ 756 self._make_tail(url) 757 result = self._format._initialize_for_clone(url) 758 self.open_repository().clone(result, revision_id=revision_id) 759 from_branch = self.open_branch() 760 from_branch.clone(result, revision_id=revision_id, tag_selector=tag_selector) 761 try: 762 tree = self.open_workingtree() 763 except errors.NotLocalUrl: 764 # make a new one, this format always has to have one. 765 result._init_workingtree() 766 else: 767 tree.clone(result) 768 return result 769 770 def create_branch(self, name=None, repository=None, 771 append_revisions_only=None): 772 """See ControlDir.create_branch.""" 773 if repository is not None: 774 raise NotImplementedError( 775 "create_branch(repository=<not None>) on %r" % (self,)) 776 return self._format.get_branch_format().initialize(self, name=name, 777 append_revisions_only=append_revisions_only) 778 779 def destroy_branch(self, name=None): 780 """See ControlDir.destroy_branch.""" 781 raise errors.UnsupportedOperation(self.destroy_branch, self) 782 783 def create_repository(self, shared=False): 784 """See ControlDir.create_repository.""" 785 if shared: 786 raise errors.IncompatibleFormat('shared repository', self._format) 787 return self.open_repository() 788 789 def destroy_repository(self): 790 """See ControlDir.destroy_repository.""" 791 raise errors.UnsupportedOperation(self.destroy_repository, self) 792 793 def create_workingtree(self, revision_id=None, from_branch=None, 794 accelerator_tree=None, hardlink=False): 795 """See ControlDir.create_workingtree.""" 796 # The workingtree is sometimes created when the bzrdir is created, 797 # but not when cloning. 798 799 # this looks buggy but is not -really- 800 # because this format creates the workingtree when the bzrdir is 801 # created 802 # clone and sprout will have set the revision_id 803 # and that will have set it for us, its only 804 # specific uses of create_workingtree in isolation 805 # that can do wonky stuff here, and that only 806 # happens for creating checkouts, which cannot be 807 # done on this format anyway. So - acceptable wart. 808 if hardlink: 809 warning("can't support hardlinked working trees in %r" 810 % (self,)) 811 try: 812 result = self.open_workingtree(recommend_upgrade=False) 813 except errors.NoSuchFile: 814 result = self._init_workingtree() 815 if revision_id is not None: 816 if revision_id == _mod_revision.NULL_REVISION: 817 result.set_parent_ids([]) 818 else: 819 result.set_parent_ids([revision_id]) 820 return result 821 822 def _init_workingtree(self): 823 from .workingtree import WorkingTreeFormat2 824 try: 825 return WorkingTreeFormat2().initialize(self) 826 except errors.NotLocalUrl: 827 # Even though we can't access the working tree, we need to 828 # create its control files. 829 return WorkingTreeFormat2()._stub_initialize_on_transport( 830 self.transport, self._control_files._file_mode) 831 832 def destroy_workingtree(self): 833 """See ControlDir.destroy_workingtree.""" 834 raise errors.UnsupportedOperation(self.destroy_workingtree, self) 835 836 def destroy_workingtree_metadata(self): 837 """See ControlDir.destroy_workingtree_metadata.""" 838 raise errors.UnsupportedOperation(self.destroy_workingtree_metadata, 839 self) 840 841 def get_branch_transport(self, branch_format, name=None): 842 """See BzrDir.get_branch_transport().""" 843 if name: 844 raise errors.NoColocatedBranchSupport(self) 845 if branch_format is None: 846 return self.transport 847 try: 848 branch_format.get_format_string() 849 except NotImplementedError: 850 return self.transport 851 raise errors.IncompatibleFormat(branch_format, self._format) 852 853 def get_repository_transport(self, repository_format): 854 """See BzrDir.get_repository_transport().""" 855 if repository_format is None: 856 return self.transport 857 try: 858 repository_format.get_format_string() 859 except NotImplementedError: 860 return self.transport 861 raise errors.IncompatibleFormat(repository_format, self._format) 862 863 def get_workingtree_transport(self, workingtree_format): 864 """See BzrDir.get_workingtree_transport().""" 865 if workingtree_format is None: 866 return self.transport 867 try: 868 workingtree_format.get_format_string() 869 except NotImplementedError: 870 return self.transport 871 raise errors.IncompatibleFormat(workingtree_format, self._format) 872 873 def needs_format_conversion(self, format): 874 """See ControlDir.needs_format_conversion().""" 875 # if the format is not the same as the system default, 876 # an upgrade is needed. 877 return not isinstance(self._format, format.__class__) 878 879 def open_branch(self, name=None, unsupported=False, 880 ignore_fallbacks=False, possible_transports=None): 881 """See ControlDir.open_branch.""" 882 from .branch import BzrBranchFormat4 883 format = BzrBranchFormat4() 884 format.check_support_status(unsupported) 885 return format.open(self, name, _found=True, 886 possible_transports=possible_transports) 887 888 def sprout(self, url, revision_id=None, force_new_repo=False, 889 recurse=None, possible_transports=None, accelerator_tree=None, 890 hardlink=False, stacked=False, create_tree_if_local=True, 891 source_branch=None): 892 """See ControlDir.sprout().""" 893 if source_branch is not None: 894 my_branch = self.open_branch() 895 if source_branch.base != my_branch.base: 896 raise AssertionError( 897 "source branch %r is not within %r with branch %r" % 898 (source_branch, self, my_branch)) 899 if stacked: 900 raise _mod_branch.UnstackableBranchFormat( 901 self._format, self.root_transport.base) 902 if not create_tree_if_local: 903 raise MustHaveWorkingTree( 904 self._format, self.root_transport.base) 905 from .workingtree import WorkingTreeFormat2 906 self._make_tail(url) 907 result = self._format._initialize_for_clone(url) 908 try: 909 self.open_repository().clone(result, revision_id=revision_id) 910 except errors.NoRepositoryPresent: 911 pass 912 try: 913 self.open_branch().sprout(result, revision_id=revision_id) 914 except errors.NotBranchError: 915 pass 916 917 # we always want a working tree 918 WorkingTreeFormat2().initialize(result, 919 accelerator_tree=accelerator_tree, 920 hardlink=hardlink) 921 return result 922 923 def set_branch_reference(self, target_branch, name=None): 924 from ...bzr.branch import BranchReferenceFormat 925 if name is not None: 926 raise errors.NoColocatedBranchSupport(self) 927 raise errors.IncompatibleFormat(BranchReferenceFormat, self._format) 928 929 930class BzrDir4(BzrDirPreSplitOut): 931 """A .bzr version 4 control object. 932 933 This is a deprecated format and may be removed after sept 2006. 934 """ 935 936 def create_repository(self, shared=False): 937 """See ControlDir.create_repository.""" 938 return self._format.repository_format.initialize(self, shared) 939 940 def needs_format_conversion(self, format): 941 """Format 4 dirs are always in need of conversion.""" 942 return True 943 944 def open_repository(self): 945 """See ControlDir.open_repository.""" 946 from .repository import RepositoryFormat4 947 return RepositoryFormat4().open(self, _found=True) 948 949 950class BzrDir5(BzrDirPreSplitOut): 951 """A .bzr version 5 control object. 952 953 This is a deprecated format and may be removed after sept 2006. 954 """ 955 956 def has_workingtree(self): 957 """See ControlDir.has_workingtree.""" 958 return True 959 960 def open_repository(self): 961 """See ControlDir.open_repository.""" 962 from .repository import RepositoryFormat5 963 return RepositoryFormat5().open(self, _found=True) 964 965 def open_workingtree(self, unsupported=False, 966 recommend_upgrade=True): 967 """See ControlDir.create_workingtree.""" 968 from .workingtree import WorkingTreeFormat2 969 wt_format = WorkingTreeFormat2() 970 # we don't warn here about upgrades; that ought to be handled for the 971 # bzrdir as a whole 972 return wt_format.open(self, _found=True) 973 974 975class BzrDir6(BzrDirPreSplitOut): 976 """A .bzr version 6 control object. 977 978 This is a deprecated format and may be removed after sept 2006. 979 """ 980 981 def has_workingtree(self): 982 """See ControlDir.has_workingtree.""" 983 return True 984 985 def open_repository(self): 986 """See ControlDir.open_repository.""" 987 from .repository import RepositoryFormat6 988 return RepositoryFormat6().open(self, _found=True) 989 990 def open_workingtree(self, unsupported=False, recommend_upgrade=True): 991 """See ControlDir.create_workingtree.""" 992 # we don't warn here about upgrades; that ought to be handled for the 993 # bzrdir as a whole 994 from .workingtree import WorkingTreeFormat2 995 return WorkingTreeFormat2().open(self, _found=True) 996