1# Copyright (C) 2007-2011 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"""Deprecated weave-based repository formats. 18 19Weave based formats scaled linearly with history size and could not represent 20ghosts. 21""" 22 23import gzip 24from io import BytesIO 25import os 26 27from ...lazy_import import lazy_import 28lazy_import(globals(), """ 29import itertools 30 31from breezy import ( 32 graph as _mod_graph, 33 ui, 34 ) 35from breezy.bzr import ( 36 xml5, 37 ) 38""") 39from ... import ( 40 debug, 41 errors, 42 lockable_files, 43 lockdir, 44 osutils, 45 trace, 46 tuned_gzip, 47 urlutils, 48 ) 49from ...bzr import ( 50 versionedfile, 51 weave, 52 weavefile, 53 ) 54from ...repository import ( 55 InterRepository, 56 ) 57from ...bzr.repository import ( 58 RepositoryFormatMetaDir, 59 ) 60from .store.text import TextStore 61from ...bzr.versionedfile import ( 62 AbsentContentFactory, 63 FulltextContentFactory, 64 VersionedFiles, 65 ) 66from ...bzr.vf_repository import ( 67 InterSameDataRepository, 68 VersionedFileCommitBuilder, 69 VersionedFileRepository, 70 VersionedFileRepositoryFormat, 71 MetaDirVersionedFileRepository, 72 MetaDirVersionedFileRepositoryFormat, 73 ) 74 75from . import bzrdir as weave_bzrdir 76 77 78class AllInOneRepository(VersionedFileRepository): 79 """Legacy support - the repository behaviour for all-in-one branches.""" 80 81 @property 82 def _serializer(self): 83 return xml5.serializer_v5 84 85 def _escape(self, file_or_path): 86 if not isinstance(file_or_path, str): 87 file_or_path = '/'.join(file_or_path) 88 if file_or_path == '': 89 return u'' 90 return urlutils.escape(osutils.safe_unicode(file_or_path)) 91 92 def __init__(self, _format, a_controldir): 93 # we reuse one control files instance. 94 dir_mode = a_controldir._get_dir_mode() 95 file_mode = a_controldir._get_file_mode() 96 97 def get_store(name, compressed=True, prefixed=False): 98 # FIXME: This approach of assuming stores are all entirely compressed 99 # or entirely uncompressed is tidy, but breaks upgrade from 100 # some existing branches where there's a mixture; we probably 101 # still want the option to look for both. 102 relpath = self._escape(name) 103 store = TextStore(a_controldir.transport.clone(relpath), 104 prefixed=prefixed, compressed=compressed, 105 dir_mode=dir_mode, 106 file_mode=file_mode) 107 return store 108 109 # not broken out yet because the controlweaves|inventory_store 110 # and texts bits are still different. 111 if isinstance(_format, RepositoryFormat4): 112 # cannot remove these - there is still no consistent api 113 # which allows access to this old info. 114 self.inventory_store = get_store('inventory-store') 115 self._text_store = get_store('text-store') 116 super(AllInOneRepository, self).__init__( 117 _format, a_controldir, a_controldir._control_files) 118 119 def _all_possible_ids(self): 120 """Return all the possible revisions that we could find.""" 121 if 'evil' in debug.debug_flags: 122 trace.mutter_callsite( 123 3, "_all_possible_ids scales with size of history.") 124 with self.lock_read(): 125 return [key[-1] for key in self.inventories.keys()] 126 127 def _all_revision_ids(self): 128 """Returns a list of all the revision ids in the repository. 129 130 These are in as much topological order as the underlying store can 131 present: for weaves ghosts may lead to a lack of correctness until 132 the reweave updates the parents list. 133 """ 134 with self.lock_read(): 135 return [key[-1] for key in self.revisions.keys()] 136 137 def _activate_new_inventory(self): 138 """Put a replacement inventory.new into use as inventories.""" 139 # Copy the content across 140 t = self.controldir._control_files._transport 141 t.copy('inventory.new.weave', 'inventory.weave') 142 # delete the temp inventory 143 t.delete('inventory.new.weave') 144 # Check we can parse the new weave properly as a sanity check 145 self.inventories.keys() 146 147 def _backup_inventory(self): 148 t = self.controldir._control_files._transport 149 t.copy('inventory.weave', 'inventory.backup.weave') 150 151 def _temp_inventories(self): 152 t = self.controldir._control_files._transport 153 return self._format._get_inventories(t, self, 'inventory.new') 154 155 def get_commit_builder(self, branch, parents, config, timestamp=None, 156 timezone=None, committer=None, revprops=None, 157 revision_id=None, lossy=False): 158 self._check_ascii_revisionid(revision_id, self.get_commit_builder) 159 result = VersionedFileCommitBuilder(self, parents, config, timestamp, 160 timezone, committer, revprops, revision_id, lossy=lossy) 161 self.start_write_group() 162 return result 163 164 def _inventory_add_lines(self, revision_id, parents, lines, 165 check_content=True): 166 """Store lines in inv_vf and return the sha1 of the inventory.""" 167 present_parents = self.get_graph().get_parent_map(parents) 168 final_parents = [] 169 for parent in parents: 170 if parent in present_parents: 171 final_parents.append((parent,)) 172 return self.inventories.add_lines((revision_id,), final_parents, lines, 173 check_content=check_content)[0] 174 175 def is_shared(self): 176 """AllInOne repositories cannot be shared.""" 177 return False 178 179 def set_make_working_trees(self, new_value): 180 """Set the policy flag for making working trees when creating branches. 181 182 This only applies to branches that use this repository. 183 184 The default is 'True'. 185 :param new_value: True to restore the default, False to disable making 186 working trees. 187 """ 188 raise errors.RepositoryUpgradeRequired(self.user_url) 189 190 def make_working_trees(self): 191 """Returns the policy for making working trees on new branches.""" 192 return True 193 194 195class WeaveMetaDirRepository(MetaDirVersionedFileRepository): 196 """A subclass of MetaDirRepository to set weave specific policy.""" 197 198 def __init__(self, _format, a_controldir, control_files): 199 super(WeaveMetaDirRepository, self).__init__( 200 _format, a_controldir, control_files) 201 self._serializer = _format._serializer 202 203 def _all_possible_ids(self): 204 """Return all the possible revisions that we could find.""" 205 if 'evil' in debug.debug_flags: 206 trace.mutter_callsite( 207 3, "_all_possible_ids scales with size of history.") 208 with self.lock_read(): 209 return [key[-1] for key in self.inventories.keys()] 210 211 def _all_revision_ids(self): 212 """Returns a list of all the revision ids in the repository. 213 214 These are in as much topological order as the underlying store can 215 present: for weaves ghosts may lead to a lack of correctness until 216 the reweave updates the parents list. 217 """ 218 with self.lock_read(): 219 return [key[-1] for key in self.revisions.keys()] 220 221 def _activate_new_inventory(self): 222 """Put a replacement inventory.new into use as inventories.""" 223 # Copy the content across 224 t = self._transport 225 t.copy('inventory.new.weave', 'inventory.weave') 226 # delete the temp inventory 227 t.delete('inventory.new.weave') 228 # Check we can parse the new weave properly as a sanity check 229 self.inventories.keys() 230 231 def _backup_inventory(self): 232 t = self._transport 233 t.copy('inventory.weave', 'inventory.backup.weave') 234 235 def _temp_inventories(self): 236 t = self._transport 237 return self._format._get_inventories(t, self, 'inventory.new') 238 239 def get_commit_builder(self, branch, parents, config, timestamp=None, 240 timezone=None, committer=None, revprops=None, 241 revision_id=None, lossy=False): 242 self._check_ascii_revisionid(revision_id, self.get_commit_builder) 243 result = VersionedFileCommitBuilder(self, parents, config, timestamp, 244 timezone, committer, revprops, revision_id, lossy=lossy) 245 self.start_write_group() 246 return result 247 248 def get_revision(self, revision_id): 249 """Return the Revision object for a named revision""" 250 with self.lock_read(): 251 return self.get_revision_reconcile(revision_id) 252 253 def _inventory_add_lines(self, revision_id, parents, lines, 254 check_content=True): 255 """Store lines in inv_vf and return the sha1 of the inventory.""" 256 present_parents = self.get_graph().get_parent_map(parents) 257 final_parents = [] 258 for parent in parents: 259 if parent in present_parents: 260 final_parents.append((parent,)) 261 return self.inventories.add_lines((revision_id,), final_parents, lines, 262 check_content=check_content)[0] 263 264 265class PreSplitOutRepositoryFormat(VersionedFileRepositoryFormat): 266 """Base class for the pre split out repository formats.""" 267 268 rich_root_data = False 269 supports_tree_reference = False 270 supports_ghosts = False 271 supports_external_lookups = False 272 supports_chks = False 273 supports_nesting_repositories = True 274 _fetch_order = 'topological' 275 _fetch_reconcile = True 276 fast_deltas = False 277 supports_leaving_lock = False 278 supports_overriding_transport = False 279 # XXX: This is an old format that we don't support full checking on, so 280 # just claim that checking for this inconsistency is not required. 281 revision_graph_can_have_wrong_parents = False 282 283 def initialize(self, a_controldir, shared=False, _internal=False): 284 """Create a weave repository.""" 285 if shared: 286 raise errors.IncompatibleFormat(self, a_controldir._format) 287 288 if not _internal: 289 # always initialized when the bzrdir is. 290 return self.open(a_controldir, _found=True) 291 292 # Create an empty weave 293 sio = BytesIO() 294 weavefile.write_weave_v5(weave.Weave(), sio) 295 empty_weave = sio.getvalue() 296 297 trace.mutter('creating repository in %s.', a_controldir.transport.base) 298 299 # FIXME: RBC 20060125 don't peek under the covers 300 # NB: no need to escape relative paths that are url safe. 301 control_files = lockable_files.LockableFiles(a_controldir.transport, 302 'branch-lock', lockable_files.TransportLock) 303 control_files.create_lock() 304 control_files.lock_write() 305 transport = a_controldir.transport 306 try: 307 transport.mkdir('revision-store', 308 mode=a_controldir._get_dir_mode()) 309 transport.mkdir('weaves', mode=a_controldir._get_dir_mode()) 310 transport.put_bytes_non_atomic('inventory.weave', empty_weave, 311 mode=a_controldir._get_file_mode()) 312 finally: 313 control_files.unlock() 314 repository = self.open(a_controldir, _found=True) 315 self._run_post_repo_init_hooks(repository, a_controldir, shared) 316 return repository 317 318 def open(self, a_controldir, _found=False): 319 """See RepositoryFormat.open().""" 320 if not _found: 321 # we are being called directly and must probe. 322 raise NotImplementedError 323 324 repo_transport = a_controldir.get_repository_transport(None) 325 result = AllInOneRepository(_format=self, a_controldir=a_controldir) 326 result.revisions = self._get_revisions(repo_transport, result) 327 result.signatures = self._get_signatures(repo_transport, result) 328 result.inventories = self._get_inventories(repo_transport, result) 329 result.texts = self._get_texts(repo_transport, result) 330 result.chk_bytes = None 331 return result 332 333 def is_deprecated(self): 334 return True 335 336 337class RepositoryFormat4(PreSplitOutRepositoryFormat): 338 """Bzr repository format 4. 339 340 This repository format has: 341 - flat stores 342 - TextStores for texts, inventories,revisions. 343 344 This format is deprecated: it indexes texts using a text id which is 345 removed in format 5; initialization and write support for this format 346 has been removed. 347 """ 348 349 supports_funky_characters = False 350 351 _matchingcontroldir = weave_bzrdir.BzrDirFormat4() 352 353 def get_format_description(self): 354 """See RepositoryFormat.get_format_description().""" 355 return "Repository format 4" 356 357 def initialize(self, url, shared=False, _internal=False): 358 """Format 4 branches cannot be created.""" 359 raise errors.UninitializableFormat(self) 360 361 def is_supported(self): 362 """Format 4 is not supported. 363 364 It is not supported because the model changed from 4 to 5 and the 365 conversion logic is expensive - so doing it on the fly was not 366 feasible. 367 """ 368 return False 369 370 def _get_inventories(self, repo_transport, repo, name='inventory'): 371 # No inventories store written so far. 372 return None 373 374 def _get_revisions(self, repo_transport, repo): 375 from .xml4 import serializer_v4 376 return RevisionTextStore(repo_transport.clone('revision-store'), 377 serializer_v4, True, versionedfile.PrefixMapper(), 378 repo.is_locked, repo.is_write_locked) 379 380 def _get_signatures(self, repo_transport, repo): 381 return SignatureTextStore(repo_transport.clone('revision-store'), 382 False, versionedfile.PrefixMapper(), 383 repo.is_locked, repo.is_write_locked) 384 385 def _get_texts(self, repo_transport, repo): 386 return None 387 388 389class RepositoryFormat5(PreSplitOutRepositoryFormat): 390 """Bzr control format 5. 391 392 This repository format has: 393 - weaves for file texts and inventory 394 - flat stores 395 - TextStores for revisions and signatures. 396 """ 397 398 _versionedfile_class = weave.WeaveFile 399 _matchingcontroldir = weave_bzrdir.BzrDirFormat5() 400 supports_funky_characters = False 401 402 @property 403 def _serializer(self): 404 return xml5.serializer_v5 405 406 def get_format_description(self): 407 """See RepositoryFormat.get_format_description().""" 408 return "Weave repository format 5" 409 410 def network_name(self): 411 """The network name for this format is the control dirs disk label.""" 412 return self._matchingcontroldir.get_format_string() 413 414 def _get_inventories(self, repo_transport, repo, name='inventory'): 415 mapper = versionedfile.ConstantMapper(name) 416 return versionedfile.ThunkedVersionedFiles(repo_transport, 417 weave.WeaveFile, mapper, repo.is_locked) 418 419 def _get_revisions(self, repo_transport, repo): 420 return RevisionTextStore(repo_transport.clone('revision-store'), 421 xml5.serializer_v5, False, versionedfile.PrefixMapper(), 422 repo.is_locked, repo.is_write_locked) 423 424 def _get_signatures(self, repo_transport, repo): 425 return SignatureTextStore(repo_transport.clone('revision-store'), 426 False, versionedfile.PrefixMapper(), 427 repo.is_locked, repo.is_write_locked) 428 429 def _get_texts(self, repo_transport, repo): 430 mapper = versionedfile.PrefixMapper() 431 base_transport = repo_transport.clone('weaves') 432 return versionedfile.ThunkedVersionedFiles(base_transport, 433 weave.WeaveFile, mapper, repo.is_locked) 434 435 436class RepositoryFormat6(PreSplitOutRepositoryFormat): 437 """Bzr control format 6. 438 439 This repository format has: 440 - weaves for file texts and inventory 441 - hash subdirectory based stores. 442 - TextStores for revisions and signatures. 443 """ 444 445 _versionedfile_class = weave.WeaveFile 446 _matchingcontroldir = weave_bzrdir.BzrDirFormat6() 447 supports_funky_characters = False 448 449 @property 450 def _serializer(self): 451 return xml5.serializer_v5 452 453 def get_format_description(self): 454 """See RepositoryFormat.get_format_description().""" 455 return "Weave repository format 6" 456 457 def network_name(self): 458 """The network name for this format is the control dirs disk label.""" 459 return self._matchingcontroldir.get_format_string() 460 461 def _get_inventories(self, repo_transport, repo, name='inventory'): 462 mapper = versionedfile.ConstantMapper(name) 463 return versionedfile.ThunkedVersionedFiles(repo_transport, 464 weave.WeaveFile, mapper, repo.is_locked) 465 466 def _get_revisions(self, repo_transport, repo): 467 return RevisionTextStore(repo_transport.clone('revision-store'), 468 xml5.serializer_v5, False, versionedfile.HashPrefixMapper(), 469 repo.is_locked, repo.is_write_locked) 470 471 def _get_signatures(self, repo_transport, repo): 472 return SignatureTextStore(repo_transport.clone('revision-store'), 473 False, versionedfile.HashPrefixMapper(), 474 repo.is_locked, repo.is_write_locked) 475 476 def _get_texts(self, repo_transport, repo): 477 mapper = versionedfile.HashPrefixMapper() 478 base_transport = repo_transport.clone('weaves') 479 return versionedfile.ThunkedVersionedFiles(base_transport, 480 weave.WeaveFile, mapper, repo.is_locked) 481 482 483class RepositoryFormat7(MetaDirVersionedFileRepositoryFormat): 484 """Bzr repository 7. 485 486 This repository format has: 487 - weaves for file texts and inventory 488 - hash subdirectory based stores. 489 - TextStores for revisions and signatures. 490 - a format marker of its own 491 - an optional 'shared-storage' flag 492 - an optional 'no-working-trees' flag 493 """ 494 495 _versionedfile_class = weave.WeaveFile 496 supports_ghosts = False 497 supports_chks = False 498 supports_funky_characters = False 499 revision_graph_can_have_wrong_parents = False 500 501 _fetch_order = 'topological' 502 _fetch_reconcile = True 503 fast_deltas = False 504 505 @property 506 def _serializer(self): 507 return xml5.serializer_v5 508 509 @classmethod 510 def get_format_string(cls): 511 """See RepositoryFormat.get_format_string().""" 512 return b"Bazaar-NG Repository format 7" 513 514 def get_format_description(self): 515 """See RepositoryFormat.get_format_description().""" 516 return "Weave repository format 7" 517 518 def _get_inventories(self, repo_transport, repo, name='inventory'): 519 mapper = versionedfile.ConstantMapper(name) 520 return versionedfile.ThunkedVersionedFiles(repo_transport, 521 weave.WeaveFile, mapper, repo.is_locked) 522 523 def _get_revisions(self, repo_transport, repo): 524 return RevisionTextStore(repo_transport.clone('revision-store'), 525 xml5.serializer_v5, True, versionedfile.HashPrefixMapper(), 526 repo.is_locked, repo.is_write_locked) 527 528 def _get_signatures(self, repo_transport, repo): 529 return SignatureTextStore(repo_transport.clone('revision-store'), 530 True, versionedfile.HashPrefixMapper(), 531 repo.is_locked, repo.is_write_locked) 532 533 def _get_texts(self, repo_transport, repo): 534 mapper = versionedfile.HashPrefixMapper() 535 base_transport = repo_transport.clone('weaves') 536 return versionedfile.ThunkedVersionedFiles(base_transport, 537 weave.WeaveFile, mapper, repo.is_locked) 538 539 def initialize(self, a_controldir, shared=False): 540 """Create a weave repository. 541 542 :param shared: If true the repository will be initialized as a shared 543 repository. 544 """ 545 # Create an empty weave 546 sio = BytesIO() 547 weavefile.write_weave_v5(weave.Weave(), sio) 548 empty_weave = sio.getvalue() 549 550 trace.mutter('creating repository in %s.', a_controldir.transport.base) 551 dirs = ['revision-store', 'weaves'] 552 files = [('inventory.weave', BytesIO(empty_weave)), 553 ] 554 utf8_files = [('format', self.get_format_string())] 555 556 self._upload_blank_content( 557 a_controldir, dirs, files, utf8_files, shared) 558 return self.open(a_controldir=a_controldir, _found=True) 559 560 def open(self, a_controldir, _found=False, _override_transport=None): 561 """See RepositoryFormat.open(). 562 563 :param _override_transport: INTERNAL USE ONLY. Allows opening the 564 repository at a slightly different url 565 than normal. I.e. during 'upgrade'. 566 """ 567 if not _found: 568 format = RepositoryFormatMetaDir.find_format(a_controldir) 569 if _override_transport is not None: 570 repo_transport = _override_transport 571 else: 572 repo_transport = a_controldir.get_repository_transport(None) 573 control_files = lockable_files.LockableFiles(repo_transport, 574 'lock', lockdir.LockDir) 575 result = WeaveMetaDirRepository(_format=self, a_controldir=a_controldir, 576 control_files=control_files) 577 result.revisions = self._get_revisions(repo_transport, result) 578 result.signatures = self._get_signatures(repo_transport, result) 579 result.inventories = self._get_inventories(repo_transport, result) 580 result.texts = self._get_texts(repo_transport, result) 581 result.chk_bytes = None 582 result._transport = repo_transport 583 return result 584 585 def is_deprecated(self): 586 return True 587 588 589class TextVersionedFiles(VersionedFiles): 590 """Just-a-bunch-of-files based VersionedFile stores.""" 591 592 def __init__(self, transport, compressed, mapper, is_locked, can_write): 593 self._compressed = compressed 594 self._transport = transport 595 self._mapper = mapper 596 if self._compressed: 597 self._ext = '.gz' 598 else: 599 self._ext = '' 600 self._is_locked = is_locked 601 self._can_write = can_write 602 603 def add_lines(self, key, parents, lines): 604 """Add a revision to the store.""" 605 if not self._is_locked(): 606 raise errors.ObjectNotLocked(self) 607 if not self._can_write(): 608 raise errors.ReadOnlyError(self) 609 if b'/' in key[-1]: 610 raise ValueError('bad idea to put / in %r' % (key,)) 611 chunks = lines 612 if self._compressed: 613 chunks = tuned_gzip.chunks_to_gzip(chunks) 614 path = self._map(key) 615 self._transport.put_file_non_atomic( 616 path, BytesIO(b''.join(chunks)), 617 create_parent_dir=True) 618 619 def insert_record_stream(self, stream): 620 adapters = {} 621 for record in stream: 622 # Raise an error when a record is missing. 623 if record.storage_kind == 'absent': 624 raise errors.RevisionNotPresent([record.key[0]], self) 625 # adapt to non-tuple interface 626 if record.storage_kind in ('fulltext', 'chunks', 'lines'): 627 self.add_lines(record.key, None, 628 record.get_bytes_as('lines')) 629 else: 630 adapter_key = record.storage_kind, 'lines' 631 try: 632 adapter = adapters[adapter_key] 633 except KeyError: 634 adapter_factory = adapter_registry.get(adapter_key) 635 adapter = adapter_factory(self) 636 adapters[adapter_key] = adapter 637 lines = adapter.get_bytes( 638 record, record.get_bytes_as(record.storage_kind)) 639 try: 640 self.add_lines(record.key, None, lines) 641 except errors.RevisionAlreadyPresent: 642 pass 643 644 def _load_text(self, key): 645 if not self._is_locked(): 646 raise errors.ObjectNotLocked(self) 647 path = self._map(key) 648 try: 649 text = self._transport.get_bytes(path) 650 compressed = self._compressed 651 except errors.NoSuchFile: 652 if self._compressed: 653 # try without the .gz 654 path = path[:-3] 655 try: 656 text = self._transport.get_bytes(path) 657 compressed = False 658 except errors.NoSuchFile: 659 return None 660 else: 661 return None 662 if compressed: 663 text = gzip.GzipFile(mode='rb', fileobj=BytesIO(text)).read() 664 return text 665 666 def _map(self, key): 667 return self._mapper.map(key) + self._ext 668 669 670class RevisionTextStore(TextVersionedFiles): 671 """Legacy thunk for format 4 repositories.""" 672 673 def __init__(self, transport, serializer, compressed, mapper, is_locked, 674 can_write): 675 """Create a RevisionTextStore at transport with serializer.""" 676 TextVersionedFiles.__init__(self, transport, compressed, mapper, 677 is_locked, can_write) 678 self._serializer = serializer 679 680 def _load_text_parents(self, key): 681 text = self._load_text(key) 682 if text is None: 683 return None, None 684 parents = self._serializer.read_revision_from_string(text).parent_ids 685 return text, tuple((parent,) for parent in parents) 686 687 def get_parent_map(self, keys): 688 result = {} 689 for key in keys: 690 parents = self._load_text_parents(key)[1] 691 if parents is None: 692 continue 693 result[key] = parents 694 return result 695 696 def get_known_graph_ancestry(self, keys): 697 """Get a KnownGraph instance with the ancestry of keys.""" 698 keys = self.keys() 699 parent_map = self.get_parent_map(keys) 700 kg = _mod_graph.KnownGraph(parent_map) 701 return kg 702 703 def get_record_stream(self, keys, sort_order, include_delta_closure): 704 for key in keys: 705 text, parents = self._load_text_parents(key) 706 if text is None: 707 yield AbsentContentFactory(key) 708 else: 709 yield FulltextContentFactory(key, parents, None, text) 710 711 def keys(self): 712 if not self._is_locked(): 713 raise errors.ObjectNotLocked(self) 714 relpaths = set() 715 for quoted_relpath in self._transport.iter_files_recursive(): 716 relpath = urlutils.unquote(quoted_relpath) 717 path, ext = os.path.splitext(relpath) 718 if ext == '.gz': 719 relpath = path 720 if not relpath.endswith('.sig'): 721 relpaths.add(relpath) 722 paths = list(relpaths) 723 return {self._mapper.unmap(path) for path in paths} 724 725 726class SignatureTextStore(TextVersionedFiles): 727 """Legacy thunk for format 4-7 repositories.""" 728 729 def __init__(self, transport, compressed, mapper, is_locked, can_write): 730 TextVersionedFiles.__init__(self, transport, compressed, mapper, 731 is_locked, can_write) 732 self._ext = '.sig' + self._ext 733 734 def get_parent_map(self, keys): 735 result = {} 736 for key in keys: 737 text = self._load_text(key) 738 if text is None: 739 continue 740 result[key] = None 741 return result 742 743 def get_record_stream(self, keys, sort_order, include_delta_closure): 744 for key in keys: 745 text = self._load_text(key) 746 if text is None: 747 yield AbsentContentFactory(key) 748 else: 749 yield FulltextContentFactory(key, None, None, text) 750 751 def keys(self): 752 if not self._is_locked(): 753 raise errors.ObjectNotLocked(self) 754 relpaths = set() 755 for quoted_relpath in self._transport.iter_files_recursive(): 756 relpath = urlutils.unquote(quoted_relpath) 757 path, ext = os.path.splitext(relpath) 758 if ext == '.gz': 759 relpath = path 760 if not relpath.endswith('.sig'): 761 continue 762 relpaths.add(relpath[:-4]) 763 paths = list(relpaths) 764 return {self._mapper.unmap(path) for path in paths} 765 766 767class InterWeaveRepo(InterSameDataRepository): 768 """Optimised code paths between Weave based repositories. 769 """ 770 771 @classmethod 772 def _get_repo_format_to_test(self): 773 return RepositoryFormat7() 774 775 @staticmethod 776 def is_compatible(source, target): 777 """Be compatible with known Weave formats. 778 779 We don't test for the stores being of specific types because that 780 could lead to confusing results, and there is no need to be 781 overly general. 782 """ 783 try: 784 return (isinstance(source._format, (RepositoryFormat5, 785 RepositoryFormat6, 786 RepositoryFormat7)) 787 and isinstance(target._format, (RepositoryFormat5, 788 RepositoryFormat6, 789 RepositoryFormat7))) 790 except AttributeError: 791 return False 792 793 def copy_content(self, revision_id=None): 794 """See InterRepository.copy_content().""" 795 with self.lock_write(): 796 # weave specific optimised path: 797 try: 798 self.target.set_make_working_trees( 799 self.source.make_working_trees()) 800 except (errors.RepositoryUpgradeRequired, NotImplementedError): 801 pass 802 # FIXME do not peek! 803 if self.source._transport.listable(): 804 with ui.ui_factory.nested_progress_bar() as pb: 805 self.target.texts.insert_record_stream( 806 self.source.texts.get_record_stream( 807 self.source.texts.keys(), 'topological', False)) 808 pb.update('Copying inventory', 0, 1) 809 self.target.inventories.insert_record_stream( 810 self.source.inventories.get_record_stream( 811 self.source.inventories.keys(), 'topological', False)) 812 self.target.signatures.insert_record_stream( 813 self.source.signatures.get_record_stream( 814 self.source.signatures.keys(), 815 'unordered', True)) 816 self.target.revisions.insert_record_stream( 817 self.source.revisions.get_record_stream( 818 self.source.revisions.keys(), 819 'topological', True)) 820 else: 821 self.target.fetch(self.source, revision_id=revision_id) 822 823 def search_missing_revision_ids(self, find_ghosts=True, revision_ids=None, 824 if_present_ids=None, limit=None): 825 """See InterRepository.search_missing_revision_ids().""" 826 with self.lock_read(): 827 # we want all revisions to satisfy revision_id in source. 828 # but we don't want to stat every file here and there. 829 # we want then, all revisions other needs to satisfy revision_id 830 # checked, but not those that we have locally. 831 # so the first thing is to get a subset of the revisions to 832 # satisfy revision_id in source, and then eliminate those that 833 # we do already have. 834 # this is slow on high latency connection to self, but as this 835 # disk format scales terribly for push anyway due to rewriting 836 # inventory.weave, this is considered acceptable. 837 # - RBC 20060209 838 source_ids_set = self._present_source_revisions_for( 839 revision_ids, if_present_ids) 840 # source_ids is the worst possible case we may need to pull. 841 # now we want to filter source_ids against what we actually 842 # have in target, but don't try to check for existence where we 843 # know we do not have a revision as that would be pointless. 844 target_ids = set(self.target._all_possible_ids()) 845 possibly_present_revisions = target_ids.intersection( 846 source_ids_set) 847 actually_present_revisions = set( 848 self.target._eliminate_revisions_not_present( 849 possibly_present_revisions)) 850 required_revisions = source_ids_set.difference( 851 actually_present_revisions) 852 if revision_ids is not None: 853 # we used get_ancestry to determine source_ids then we are 854 # assured all revisions referenced are present as they are 855 # installed in topological order. and the tip revision was 856 # validated by get_ancestry. 857 result_set = required_revisions 858 else: 859 # if we just grabbed the possibly available ids, then 860 # we only have an estimate of whats available and need to 861 # validate that against the revision records. 862 result_set = set( 863 self.source._eliminate_revisions_not_present( 864 required_revisions)) 865 if limit is not None: 866 topo_ordered = self.source.get_graph().iter_topo_order(result_set) 867 result_set = set(itertools.islice(topo_ordered, limit)) 868 return self.source.revision_ids_to_search_result(result_set) 869 870 871InterRepository.register_optimiser(InterWeaveRepo) 872 873 874def get_extra_interrepo_test_combinations(): 875 from ...bzr import knitrepo 876 return [(InterRepository, RepositoryFormat5(), 877 knitrepo.RepositoryFormatKnit3())] 878