1# Copyright (C) 2005-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 17from .lazy_import import lazy_import 18lazy_import(globals(), """ 19import time 20 21from breezy import ( 22 config, 23 controldir, 24 debug, 25 graph, 26 osutils, 27 revision as _mod_revision, 28 gpg, 29 ) 30from breezy.i18n import gettext 31""") 32 33from . import ( 34 errors, 35 registry, 36 ui, 37 ) 38from .decorators import only_raises 39from .inter import InterObject 40from .lock import _RelockDebugMixin, LogicalLockResult 41from .trace import ( 42 log_exception_quietly, note, mutter, mutter_callsite, warning) 43 44 45# Old formats display a warning, but only once 46_deprecation_warning_done = False 47 48 49class IsInWriteGroupError(errors.InternalBzrError): 50 51 _fmt = "May not refresh_data of repo %(repo)s while in a write group." 52 53 def __init__(self, repo): 54 errors.InternalBzrError.__init__(self, repo=repo) 55 56 57class CannotSetRevisionId(errors.BzrError): 58 59 _fmt = "Repository format does not support setting revision ids." 60 61 62class FetchResult(object): 63 """Result of a fetch operation. 64 65 :ivar revidmap: For lossy fetches, map from source revid to target revid. 66 :ivar total_fetched: Number of revisions fetched 67 """ 68 69 def __init__(self, total_fetched=None, revidmap=None): 70 self.total_fetched = total_fetched 71 self.revidmap = revidmap 72 73 74class CommitBuilder(object): 75 """Provides an interface to build up a commit. 76 77 This allows describing a tree to be committed without needing to 78 know the internals of the format of the repository. 79 """ 80 81 # all clients should supply tree roots. 82 record_root_entry = True 83 # whether this commit builder will automatically update the branch that is 84 # being committed to 85 updates_branch = False 86 87 def __init__(self, repository, parents, config_stack, timestamp=None, 88 timezone=None, committer=None, revprops=None, 89 revision_id=None, lossy=False): 90 """Initiate a CommitBuilder. 91 92 :param repository: Repository to commit to. 93 :param parents: Revision ids of the parents of the new revision. 94 :param timestamp: Optional timestamp recorded for commit. 95 :param timezone: Optional timezone for timestamp. 96 :param committer: Optional committer to set for commit. 97 :param revprops: Optional dictionary of revision properties. 98 :param revision_id: Optional revision id. 99 :param lossy: Whether to discard data that can not be natively 100 represented, when pushing to a foreign VCS 101 """ 102 self._config_stack = config_stack 103 self._lossy = lossy 104 105 if committer is None: 106 self._committer = self._config_stack.get('email') 107 elif not isinstance(committer, str): 108 self._committer = committer.decode() # throw if non-ascii 109 else: 110 self._committer = committer 111 112 self.parents = parents 113 self.repository = repository 114 115 self._revprops = {} 116 if revprops is not None: 117 self._validate_revprops(revprops) 118 self._revprops.update(revprops) 119 120 if timestamp is None: 121 timestamp = time.time() 122 # Restrict resolution to 1ms 123 self._timestamp = round(timestamp, 3) 124 125 if timezone is None: 126 self._timezone = osutils.local_time_offset() 127 else: 128 self._timezone = int(timezone) 129 130 self._generate_revision_if_needed(revision_id) 131 132 def any_changes(self): 133 """Return True if any entries were changed. 134 135 This includes merge-only changes. It is the core for the --unchanged 136 detection in commit. 137 138 :return: True if any changes have occured. 139 """ 140 raise NotImplementedError(self.any_changes) 141 142 def _validate_unicode_text(self, text, context): 143 """Verify things like commit messages don't have bogus characters.""" 144 # TODO(jelmer): Make this repository-format specific 145 if u'\r' in text: 146 raise ValueError('Invalid value for %s: %r' % (context, text)) 147 148 def _validate_revprops(self, revprops): 149 for key, value in revprops.items(): 150 # We know that the XML serializers do not round trip '\r' 151 # correctly, so refuse to accept them 152 if not isinstance(value, str): 153 raise ValueError('revision property (%s) is not a valid' 154 ' (unicode) string: %r' % (key, value)) 155 # TODO(jelmer): Make this repository-format specific 156 self._validate_unicode_text(value, 157 'revision property (%s)' % (key,)) 158 159 def commit(self, message): 160 """Make the actual commit. 161 162 :return: The revision id of the recorded revision. 163 """ 164 raise NotImplementedError(self.commit) 165 166 def abort(self): 167 """Abort the commit that is being built. 168 """ 169 raise NotImplementedError(self.abort) 170 171 def revision_tree(self): 172 """Return the tree that was just committed. 173 174 After calling commit() this can be called to get a 175 RevisionTree representing the newly committed tree. This is 176 preferred to calling Repository.revision_tree() because that may 177 require deserializing the inventory, while we already have a copy in 178 memory. 179 """ 180 raise NotImplementedError(self.revision_tree) 181 182 def finish_inventory(self): 183 """Tell the builder that the inventory is finished. 184 185 :return: The inventory id in the repository, which can be used with 186 repository.get_inventory. 187 """ 188 raise NotImplementedError(self.finish_inventory) 189 190 def _generate_revision_if_needed(self, revision_id): 191 """Create a revision id if None was supplied. 192 193 If the repository can not support user-specified revision ids 194 they should override this function and raise CannotSetRevisionId 195 if _new_revision_id is not None. 196 197 :raises: CannotSetRevisionId 198 """ 199 if not self.repository._format.supports_setting_revision_ids: 200 if revision_id is not None: 201 raise CannotSetRevisionId() 202 return 203 if revision_id is None: 204 self._new_revision_id = self._gen_revision_id() 205 self.random_revid = True 206 else: 207 self._new_revision_id = revision_id 208 self.random_revid = False 209 210 def record_iter_changes(self, tree, basis_revision_id, iter_changes): 211 """Record a new tree via iter_changes. 212 213 :param tree: The tree to obtain text contents from for changed objects. 214 :param basis_revision_id: The revision id of the tree the iter_changes 215 has been generated against. Currently assumed to be the same 216 as self.parents[0] - if it is not, errors may occur. 217 :param iter_changes: An iter_changes iterator with the changes to apply 218 to basis_revision_id. The iterator must not include any items with 219 a current kind of None - missing items must be either filtered out 220 or errored-on beefore record_iter_changes sees the item. 221 :return: A generator of (relpath, fs_hash) tuples for use with 222 tree._observed_sha1. 223 """ 224 raise NotImplementedError(self.record_iter_changes) 225 226 227class RepositoryWriteLockResult(LogicalLockResult): 228 """The result of write locking a repository. 229 230 :ivar repository_token: The token obtained from the underlying lock, or 231 None. 232 :ivar unlock: A callable which will unlock the lock. 233 """ 234 235 def __init__(self, unlock, repository_token): 236 LogicalLockResult.__init__(self, unlock) 237 self.repository_token = repository_token 238 239 def __repr__(self): 240 return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token, 241 self.unlock) 242 243 244class WriteGroup(object): 245 """Context manager that manages a write group. 246 247 Raising an exception will result in the write group being aborted. 248 """ 249 250 def __init__(self, repository, suppress_errors=False): 251 self.repository = repository 252 self._suppress_errors = suppress_errors 253 254 def __enter__(self): 255 self.repository.start_write_group() 256 return self 257 258 def __exit__(self, exc_type, exc_val, exc_tb): 259 if exc_type: 260 self.repository.abort_write_group(self._suppress_errors) 261 return False 262 else: 263 self.repository.commit_write_group() 264 265 266###################################################################### 267# Repositories 268 269 270class Repository(controldir.ControlComponent, _RelockDebugMixin): 271 """Repository holding history for one or more branches. 272 273 The repository holds and retrieves historical information including 274 revisions and file history. It's normally accessed only by the Branch, 275 which views a particular line of development through that history. 276 277 See VersionedFileRepository in breezy.vf_repository for the 278 base class for most Bazaar repositories. 279 """ 280 281 # Does this repository implementation support random access to 282 # items in the tree, or just bulk fetching/pushing of data? 283 supports_random_access = True 284 285 def abort_write_group(self, suppress_errors=False): 286 """Commit the contents accrued within the current write group. 287 288 :param suppress_errors: if true, abort_write_group will catch and log 289 unexpected errors that happen during the abort, rather than 290 allowing them to propagate. Defaults to False. 291 292 :seealso: start_write_group. 293 """ 294 if self._write_group is not self.get_transaction(): 295 # has an unlock or relock occured ? 296 if suppress_errors: 297 mutter( 298 '(suppressed) mismatched lock context and write group. %r, %r', 299 self._write_group, self.get_transaction()) 300 return 301 raise errors.BzrError( 302 'mismatched lock context and write group. %r, %r' % 303 (self._write_group, self.get_transaction())) 304 try: 305 self._abort_write_group() 306 except Exception as exc: 307 self._write_group = None 308 if not suppress_errors: 309 raise 310 mutter('abort_write_group failed') 311 log_exception_quietly() 312 note(gettext('brz: ERROR (ignored): %s'), exc) 313 self._write_group = None 314 315 def _abort_write_group(self): 316 """Template method for per-repository write group cleanup. 317 318 This is called during abort before the write group is considered to be 319 finished and should cleanup any internal state accrued during the write 320 group. There is no requirement that data handed to the repository be 321 *not* made available - this is not a rollback - but neither should any 322 attempt be made to ensure that data added is fully commited. Abort is 323 invoked when an error has occured so futher disk or network operations 324 may not be possible or may error and if possible should not be 325 attempted. 326 """ 327 328 def add_fallback_repository(self, repository): 329 """Add a repository to use for looking up data not held locally. 330 331 :param repository: A repository. 332 """ 333 raise NotImplementedError(self.add_fallback_repository) 334 335 def _check_fallback_repository(self, repository): 336 """Check that this repository can fallback to repository safely. 337 338 Raise an error if not. 339 340 :param repository: A repository to fallback to. 341 """ 342 return InterRepository._assert_same_model(self, repository) 343 344 def all_revision_ids(self): 345 """Returns a list of all the revision ids in the repository. 346 347 This is conceptually deprecated because code should generally work on 348 the graph reachable from a particular revision, and ignore any other 349 revisions that might be present. There is no direct replacement 350 method. 351 """ 352 if 'evil' in debug.debug_flags: 353 mutter_callsite(2, "all_revision_ids is linear with history.") 354 return self._all_revision_ids() 355 356 def _all_revision_ids(self): 357 """Returns a list of all the revision ids in the repository. 358 359 These are in as much topological order as the underlying store can 360 present. 361 """ 362 raise NotImplementedError(self._all_revision_ids) 363 364 def break_lock(self): 365 """Break a lock if one is present from another instance. 366 367 Uses the ui factory to ask for confirmation if the lock may be from 368 an active process. 369 """ 370 self.control_files.break_lock() 371 372 @staticmethod 373 def create(controldir): 374 """Construct the current default format repository in controldir.""" 375 return RepositoryFormat.get_default_format().initialize(controldir) 376 377 def __init__(self, _format, controldir, control_files): 378 """instantiate a Repository. 379 380 :param _format: The format of the repository on disk. 381 :param controldir: The ControlDir of the repository. 382 :param control_files: Control files to use for locking, etc. 383 """ 384 # In the future we will have a single api for all stores for 385 # getting file texts, inventories and revisions, then 386 # this construct will accept instances of those things. 387 super(Repository, self).__init__() 388 self._format = _format 389 # the following are part of the public API for Repository: 390 self.controldir = controldir 391 self.control_files = control_files 392 # for tests 393 self._write_group = None 394 # Additional places to query for data. 395 self._fallback_repositories = [] 396 397 @property 398 def user_transport(self): 399 return self.controldir.user_transport 400 401 @property 402 def control_transport(self): 403 return self._transport 404 405 def __repr__(self): 406 if self._fallback_repositories: 407 return '%s(%r, fallback_repositories=%r)' % ( 408 self.__class__.__name__, 409 self.base, 410 self._fallback_repositories) 411 else: 412 return '%s(%r)' % (self.__class__.__name__, 413 self.base) 414 415 def _has_same_fallbacks(self, other_repo): 416 """Returns true if the repositories have the same fallbacks.""" 417 my_fb = self._fallback_repositories 418 other_fb = other_repo._fallback_repositories 419 if len(my_fb) != len(other_fb): 420 return False 421 for f, g in zip(my_fb, other_fb): 422 if not f.has_same_location(g): 423 return False 424 return True 425 426 def has_same_location(self, other): 427 """Returns a boolean indicating if this repository is at the same 428 location as another repository. 429 430 This might return False even when two repository objects are accessing 431 the same physical repository via different URLs. 432 """ 433 if self.__class__ is not other.__class__: 434 return False 435 return (self.control_url == other.control_url) 436 437 def is_in_write_group(self): 438 """Return True if there is an open write group. 439 440 :seealso: start_write_group. 441 """ 442 return self._write_group is not None 443 444 def is_locked(self): 445 return self.control_files.is_locked() 446 447 def is_write_locked(self): 448 """Return True if this object is write locked.""" 449 return self.is_locked() and self.control_files._lock_mode == 'w' 450 451 def lock_write(self, token=None): 452 """Lock this repository for writing. 453 454 This causes caching within the repository obejct to start accumlating 455 data during reads, and allows a 'write_group' to be obtained. Write 456 groups must be used for actual data insertion. 457 458 A token should be passed in if you know that you have locked the object 459 some other way, and need to synchronise this object's state with that 460 fact. 461 462 XXX: this docstring is duplicated in many places, e.g. lockable_files.py 463 464 :param token: if this is already locked, then lock_write will fail 465 unless the token matches the existing lock. 466 :returns: a token if this instance supports tokens, otherwise None. 467 :raises TokenLockingNotSupported: when a token is given but this 468 instance doesn't support using token locks. 469 :raises MismatchedToken: if the specified token doesn't match the token 470 of the existing lock. 471 :seealso: start_write_group. 472 :return: A RepositoryWriteLockResult. 473 """ 474 locked = self.is_locked() 475 token = self.control_files.lock_write(token=token) 476 if not locked: 477 self._warn_if_deprecated() 478 self._note_lock('w') 479 for repo in self._fallback_repositories: 480 # Writes don't affect fallback repos 481 repo.lock_read() 482 self._refresh_data() 483 return RepositoryWriteLockResult(self.unlock, token) 484 485 def lock_read(self): 486 """Lock the repository for read operations. 487 488 :return: An object with an unlock method which will release the lock 489 obtained. 490 """ 491 locked = self.is_locked() 492 self.control_files.lock_read() 493 if not locked: 494 self._warn_if_deprecated() 495 self._note_lock('r') 496 for repo in self._fallback_repositories: 497 repo.lock_read() 498 self._refresh_data() 499 return LogicalLockResult(self.unlock) 500 501 def get_physical_lock_status(self): 502 return self.control_files.get_physical_lock_status() 503 504 def leave_lock_in_place(self): 505 """Tell this repository not to release the physical lock when this 506 object is unlocked. 507 508 If lock_write doesn't return a token, then this method is not supported. 509 """ 510 self.control_files.leave_in_place() 511 512 def dont_leave_lock_in_place(self): 513 """Tell this repository to release the physical lock when this 514 object is unlocked, even if it didn't originally acquire it. 515 516 If lock_write doesn't return a token, then this method is not supported. 517 """ 518 self.control_files.dont_leave_in_place() 519 520 def gather_stats(self, revid=None, committers=None): 521 """Gather statistics from a revision id. 522 523 :param revid: The revision id to gather statistics from, if None, then 524 no revision specific statistics are gathered. 525 :param committers: Optional parameter controlling whether to grab 526 a count of committers from the revision specific statistics. 527 :return: A dictionary of statistics. Currently this contains: 528 committers: The number of committers if requested. 529 firstrev: A tuple with timestamp, timezone for the penultimate left 530 most ancestor of revid, if revid is not the NULL_REVISION. 531 latestrev: A tuple with timestamp, timezone for revid, if revid is 532 not the NULL_REVISION. 533 revisions: The total revision count in the repository. 534 size: An estimate disk size of the repository in bytes. 535 """ 536 with self.lock_read(): 537 result = {} 538 if revid and committers: 539 result['committers'] = 0 540 if revid and revid != _mod_revision.NULL_REVISION: 541 graph = self.get_graph() 542 if committers: 543 all_committers = set() 544 revisions = [r for (r, p) in graph.iter_ancestry([revid]) 545 if r != _mod_revision.NULL_REVISION] 546 last_revision = None 547 if not committers: 548 # ignore the revisions in the middle - just grab first and last 549 revisions = revisions[0], revisions[-1] 550 for revision in self.get_revisions(revisions): 551 if not last_revision: 552 last_revision = revision 553 if committers: 554 all_committers.add(revision.committer) 555 first_revision = revision 556 if committers: 557 result['committers'] = len(all_committers) 558 result['firstrev'] = (first_revision.timestamp, 559 first_revision.timezone) 560 result['latestrev'] = (last_revision.timestamp, 561 last_revision.timezone) 562 return result 563 564 def find_branches(self, using=False): 565 """Find branches underneath this repository. 566 567 This will include branches inside other branches. 568 569 :param using: If True, list only branches using this repository. 570 """ 571 if using and not self.is_shared(): 572 for branch in self.controldir.list_branches(): 573 yield branch 574 return 575 576 class Evaluator(object): 577 578 def __init__(self): 579 self.first_call = True 580 581 def __call__(self, controldir): 582 # On the first call, the parameter is always the controldir 583 # containing the current repo. 584 if not self.first_call: 585 try: 586 repository = controldir.open_repository() 587 except errors.NoRepositoryPresent: 588 pass 589 else: 590 return False, ([], repository) 591 self.first_call = False 592 value = (controldir.list_branches(), None) 593 return True, value 594 595 for branches, repository in controldir.ControlDir.find_controldirs( 596 self.user_transport, evaluate=Evaluator()): 597 if branches is not None: 598 for branch in branches: 599 yield branch 600 if not using and repository is not None: 601 for branch in repository.find_branches(): 602 yield branch 603 604 def search_missing_revision_ids(self, other, 605 find_ghosts=True, revision_ids=None, if_present_ids=None, 606 limit=None): 607 """Return the revision ids that other has that this does not. 608 609 These are returned in topological order. 610 611 revision_ids: only return revision ids included by revision_id. 612 """ 613 with self.lock_read(): 614 return InterRepository.get(other, self).search_missing_revision_ids( 615 find_ghosts=find_ghosts, revision_ids=revision_ids, 616 if_present_ids=if_present_ids, limit=limit) 617 618 @staticmethod 619 def open(base): 620 """Open the repository rooted at base. 621 622 For instance, if the repository is at URL/.bzr/repository, 623 Repository.open(URL) -> a Repository instance. 624 """ 625 control = controldir.ControlDir.open(base) 626 return control.open_repository() 627 628 def copy_content_into(self, destination, revision_id=None): 629 """Make a complete copy of the content in self into destination. 630 631 This is a destructive operation! Do not use it on existing 632 repositories. 633 """ 634 return InterRepository.get(self, destination).copy_content(revision_id) 635 636 def commit_write_group(self): 637 """Commit the contents accrued within the current write group. 638 639 :seealso: start_write_group. 640 641 :return: it may return an opaque hint that can be passed to 'pack'. 642 """ 643 if self._write_group is not self.get_transaction(): 644 # has an unlock or relock occured ? 645 raise errors.BzrError('mismatched lock context %r and ' 646 'write group %r.' % 647 (self.get_transaction(), self._write_group)) 648 result = self._commit_write_group() 649 self._write_group = None 650 return result 651 652 def _commit_write_group(self): 653 """Template method for per-repository write group cleanup. 654 655 This is called before the write group is considered to be 656 finished and should ensure that all data handed to the repository 657 for writing during the write group is safely committed (to the 658 extent possible considering file system caching etc). 659 """ 660 661 def suspend_write_group(self): 662 """Suspend a write group. 663 664 :raise UnsuspendableWriteGroup: If the write group can not be 665 suspended. 666 :return: List of tokens 667 """ 668 raise errors.UnsuspendableWriteGroup(self) 669 670 def refresh_data(self): 671 """Re-read any data needed to synchronise with disk. 672 673 This method is intended to be called after another repository instance 674 (such as one used by a smart server) has inserted data into the 675 repository. On all repositories this will work outside of write groups. 676 Some repository formats (pack and newer for breezy native formats) 677 support refresh_data inside write groups. If called inside a write 678 group on a repository that does not support refreshing in a write group 679 IsInWriteGroupError will be raised. 680 """ 681 self._refresh_data() 682 683 def resume_write_group(self, tokens): 684 if not self.is_write_locked(): 685 raise errors.NotWriteLocked(self) 686 if self._write_group: 687 raise errors.BzrError('already in a write group') 688 self._resume_write_group(tokens) 689 # so we can detect unlock/relock - the write group is now entered. 690 self._write_group = self.get_transaction() 691 692 def _resume_write_group(self, tokens): 693 raise errors.UnsuspendableWriteGroup(self) 694 695 def fetch(self, source, revision_id=None, find_ghosts=False, lossy=False): 696 """Fetch the content required to construct revision_id from source. 697 698 If revision_id is None, then all content is copied. 699 700 fetch() may not be used when the repository is in a write group - 701 either finish the current write group before using fetch, or use 702 fetch before starting the write group. 703 704 :param find_ghosts: Find and copy revisions in the source that are 705 ghosts in the target (and not reachable directly by walking out to 706 the first-present revision in target from revision_id). 707 :param revision_id: If specified, all the content needed for this 708 revision ID will be copied to the target. Fetch will determine for 709 itself which content needs to be copied. 710 :return: A FetchResult object 711 """ 712 if self.is_in_write_group(): 713 raise errors.InternalBzrError( 714 "May not fetch while in a write group.") 715 # fast path same-url fetch operations 716 # TODO: lift out to somewhere common with RemoteRepository 717 # <https://bugs.launchpad.net/bzr/+bug/401646> 718 if (self.has_same_location(source) 719 and self._has_same_fallbacks(source)): 720 # check that last_revision is in 'from' and then return a 721 # no-operation. 722 if (revision_id is not None and 723 not _mod_revision.is_null(revision_id)): 724 self.get_revision(revision_id) 725 return 0, [] 726 inter = InterRepository.get(source, self) 727 return inter.fetch( 728 revision_id=revision_id, find_ghosts=find_ghosts, lossy=lossy) 729 730 def get_commit_builder(self, branch, parents, config_stack, timestamp=None, 731 timezone=None, committer=None, revprops=None, 732 revision_id=None, lossy=False): 733 """Obtain a CommitBuilder for this repository. 734 735 :param branch: Branch to commit to. 736 :param parents: Revision ids of the parents of the new revision. 737 :param config_stack: Configuration stack to use. 738 :param timestamp: Optional timestamp recorded for commit. 739 :param timezone: Optional timezone for timestamp. 740 :param committer: Optional committer to set for commit. 741 :param revprops: Optional dictionary of revision properties. 742 :param revision_id: Optional revision id. 743 :param lossy: Whether to discard data that can not be natively 744 represented, when pushing to a foreign VCS 745 """ 746 raise NotImplementedError(self.get_commit_builder) 747 748 @only_raises(errors.LockNotHeld, errors.LockBroken) 749 def unlock(self): 750 if (self.control_files._lock_count == 1 and 751 self.control_files._lock_mode == 'w'): 752 if self._write_group is not None: 753 self.abort_write_group() 754 self.control_files.unlock() 755 raise errors.BzrError( 756 'Must end write groups before releasing write locks.') 757 self.control_files.unlock() 758 if self.control_files._lock_count == 0: 759 for repo in self._fallback_repositories: 760 repo.unlock() 761 762 def clone(self, controldir, revision_id=None): 763 """Clone this repository into controldir using the current format. 764 765 Currently no check is made that the format of this repository and 766 the bzrdir format are compatible. FIXME RBC 20060201. 767 768 :return: The newly created destination repository. 769 """ 770 with self.lock_read(): 771 # TODO: deprecate after 0.16; cloning this with all its settings is 772 # probably not very useful -- mbp 20070423 773 dest_repo = self._create_sprouting_repo( 774 controldir, shared=self.is_shared()) 775 self.copy_content_into(dest_repo, revision_id) 776 return dest_repo 777 778 def start_write_group(self): 779 """Start a write group in the repository. 780 781 Write groups are used by repositories which do not have a 1:1 mapping 782 between file ids and backend store to manage the insertion of data from 783 both fetch and commit operations. 784 785 A write lock is required around the 786 start_write_group/commit_write_group for the support of lock-requiring 787 repository formats. 788 789 One can only insert data into a repository inside a write group. 790 791 :return: None. 792 """ 793 if not self.is_write_locked(): 794 raise errors.NotWriteLocked(self) 795 if self._write_group: 796 raise errors.BzrError('already in a write group') 797 self._start_write_group() 798 # so we can detect unlock/relock - the write group is now entered. 799 self._write_group = self.get_transaction() 800 801 def _start_write_group(self): 802 """Template method for per-repository write group startup. 803 804 This is called before the write group is considered to be 805 entered. 806 """ 807 808 def sprout(self, to_bzrdir, revision_id=None): 809 """Create a descendent repository for new development. 810 811 Unlike clone, this does not copy the settings of the repository. 812 """ 813 with self.lock_read(): 814 dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False) 815 dest_repo.fetch(self, revision_id=revision_id) 816 return dest_repo 817 818 def _create_sprouting_repo(self, a_controldir, shared): 819 if not isinstance( 820 a_controldir._format, self.controldir._format.__class__): 821 # use target default format. 822 dest_repo = a_controldir.create_repository() 823 else: 824 # Most control formats need the repository to be specifically 825 # created, but on some old all-in-one formats it's not needed 826 try: 827 dest_repo = self._format.initialize( 828 a_controldir, shared=shared) 829 except errors.UninitializableFormat: 830 dest_repo = a_controldir.open_repository() 831 return dest_repo 832 833 def has_revision(self, revision_id): 834 """True if this repository has a copy of the revision.""" 835 with self.lock_read(): 836 return revision_id in self.has_revisions((revision_id,)) 837 838 def has_revisions(self, revision_ids): 839 """Probe to find out the presence of multiple revisions. 840 841 :param revision_ids: An iterable of revision_ids. 842 :return: A set of the revision_ids that were present. 843 """ 844 raise NotImplementedError(self.has_revisions) 845 846 def get_revision(self, revision_id): 847 """Return the Revision object for a named revision.""" 848 with self.lock_read(): 849 return self.get_revisions([revision_id])[0] 850 851 def get_revision_reconcile(self, revision_id): 852 """'reconcile' helper routine that allows access to a revision always. 853 854 This variant of get_revision does not cross check the weave graph 855 against the revision one as get_revision does: but it should only 856 be used by reconcile, or reconcile-alike commands that are correcting 857 or testing the revision graph. 858 """ 859 raise NotImplementedError(self.get_revision_reconcile) 860 861 def get_revisions(self, revision_ids): 862 """Get many revisions at once. 863 864 Repositories that need to check data on every revision read should 865 subclass this method. 866 """ 867 revs = {} 868 for revid, rev in self.iter_revisions(revision_ids): 869 if rev is None: 870 raise errors.NoSuchRevision(self, revid) 871 revs[revid] = rev 872 return [revs[revid] for revid in revision_ids] 873 874 def iter_revisions(self, revision_ids): 875 """Iterate over revision objects. 876 877 :param revision_ids: An iterable of revisions to examine. None may be 878 passed to request all revisions known to the repository. Note that 879 not all repositories can find unreferenced revisions; for those 880 repositories only referenced ones will be returned. 881 :return: An iterator of (revid, revision) tuples. Absent revisions ( 882 those asked for but not available) are returned as (revid, None). 883 N.B.: Revisions are not necessarily yielded in order. 884 """ 885 raise NotImplementedError(self.iter_revisions) 886 887 def get_revision_delta(self, revision_id): 888 """Return the delta for one revision. 889 890 The delta is relative to the left-hand predecessor of the 891 revision. 892 """ 893 with self.lock_read(): 894 r = self.get_revision(revision_id) 895 return list(self.get_revision_deltas([r]))[0] 896 897 def get_revision_deltas(self, revisions, specific_files=None): 898 """Produce a generator of revision deltas. 899 900 Note that the input is a sequence of REVISIONS, not revision ids. 901 Trees will be held in memory until the generator exits. 902 Each delta is relative to the revision's lefthand predecessor. 903 904 specific_files should exist in the first revision. 905 906 :param specific_files: if not None, the result is filtered 907 so that only those files, their parents and their 908 children are included. 909 """ 910 from .tree import InterTree 911 # Get the revision-ids of interest 912 required_trees = set() 913 for revision in revisions: 914 required_trees.add(revision.revision_id) 915 required_trees.update(revision.parent_ids[:1]) 916 917 trees = { 918 t.get_revision_id(): t 919 for t in self.revision_trees(required_trees)} 920 921 # Calculate the deltas 922 for revision in revisions: 923 if not revision.parent_ids: 924 old_tree = self.revision_tree(_mod_revision.NULL_REVISION) 925 else: 926 old_tree = trees[revision.parent_ids[0]] 927 intertree = InterTree.get(old_tree, trees[revision.revision_id]) 928 yield intertree.compare(specific_files=specific_files) 929 if specific_files is not None: 930 specific_files = [ 931 p for p in intertree.find_source_paths( 932 specific_files).values() 933 if p is not None] 934 935 def store_revision_signature(self, gpg_strategy, plaintext, revision_id): 936 raise NotImplementedError(self.store_revision_signature) 937 938 def add_signature_text(self, revision_id, signature): 939 """Store a signature text for a revision. 940 941 :param revision_id: Revision id of the revision 942 :param signature: Signature text. 943 """ 944 raise NotImplementedError(self.add_signature_text) 945 946 def iter_files_bytes(self, desired_files): 947 """Iterate through file versions. 948 949 Files will not necessarily be returned in the order they occur in 950 desired_files. No specific order is guaranteed. 951 952 Yields pairs of identifier, bytes_iterator. identifier is an opaque 953 value supplied by the caller as part of desired_files. It should 954 uniquely identify the file version in the caller's context. (Examples: 955 an index number or a TreeTransform trans_id.) 956 957 :param desired_files: a list of (file_id, revision_id, identifier) 958 triples 959 """ 960 raise NotImplementedError(self.iter_files_bytes) 961 962 def get_rev_id_for_revno(self, revno, known_pair): 963 """Return the revision id of a revno, given a later (revno, revid) 964 pair in the same history. 965 966 :return: if found (True, revid). If the available history ran out 967 before reaching the revno, then this returns 968 (False, (closest_revno, closest_revid)). 969 """ 970 known_revno, known_revid = known_pair 971 partial_history = [known_revid] 972 distance_from_known = known_revno - revno 973 if distance_from_known < 0: 974 raise errors.RevnoOutOfBounds(revno, (0, known_revno)) 975 try: 976 _iter_for_revno( 977 self, partial_history, stop_index=distance_from_known) 978 except errors.RevisionNotPresent as err: 979 if err.revision_id == known_revid: 980 # The start revision (known_revid) wasn't found. 981 raise errors.NoSuchRevision(self, known_revid) 982 # This is a stacked repository with no fallbacks, or a there's a 983 # left-hand ghost. Either way, even though the revision named in 984 # the error isn't in this repo, we know it's the next step in this 985 # left-hand history. 986 partial_history.append(err.revision_id) 987 if len(partial_history) <= distance_from_known: 988 # Didn't find enough history to get a revid for the revno. 989 earliest_revno = known_revno - len(partial_history) + 1 990 return (False, (earliest_revno, partial_history[-1])) 991 if len(partial_history) - 1 > distance_from_known: 992 raise AssertionError('_iter_for_revno returned too much history') 993 return (True, partial_history[-1]) 994 995 def is_shared(self): 996 """Return True if this repository is flagged as a shared repository.""" 997 raise NotImplementedError(self.is_shared) 998 999 def reconcile(self, other=None, thorough=False): 1000 """Reconcile this repository.""" 1001 raise NotImplementedError(self.reconcile) 1002 1003 def _refresh_data(self): 1004 """Helper called from lock_* to ensure coherency with disk. 1005 1006 The default implementation does nothing; it is however possible 1007 for repositories to maintain loaded indices across multiple locks 1008 by checking inside their implementation of this method to see 1009 whether their indices are still valid. This depends of course on 1010 the disk format being validatable in this manner. This method is 1011 also called by the refresh_data() public interface to cause a refresh 1012 to occur while in a write lock so that data inserted by a smart server 1013 push operation is visible on the client's instance of the physical 1014 repository. 1015 """ 1016 1017 def revision_tree(self, revision_id): 1018 """Return Tree for a revision on this branch. 1019 1020 `revision_id` may be NULL_REVISION for the empty tree revision. 1021 """ 1022 raise NotImplementedError(self.revision_tree) 1023 1024 def revision_trees(self, revision_ids): 1025 """Return Trees for revisions in this repository. 1026 1027 :param revision_ids: a sequence of revision-ids; 1028 a revision-id may not be None or b'null:' 1029 """ 1030 raise NotImplementedError(self.revision_trees) 1031 1032 def pack(self, hint=None, clean_obsolete_packs=False): 1033 """Compress the data within the repository. 1034 1035 This operation only makes sense for some repository types. For other 1036 types it should be a no-op that just returns. 1037 1038 This stub method does not require a lock, but subclasses should use 1039 self.write_lock as this is a long running call it's reasonable to 1040 implicitly lock for the user. 1041 1042 :param hint: If not supplied, the whole repository is packed. 1043 If supplied, the repository may use the hint parameter as a 1044 hint for the parts of the repository to pack. A hint can be 1045 obtained from the result of commit_write_group(). Out of 1046 date hints are simply ignored, because concurrent operations 1047 can obsolete them rapidly. 1048 1049 :param clean_obsolete_packs: Clean obsolete packs immediately after 1050 the pack operation. 1051 """ 1052 1053 def get_transaction(self): 1054 return self.control_files.get_transaction() 1055 1056 def get_parent_map(self, revision_ids): 1057 """See graph.StackedParentsProvider.get_parent_map""" 1058 raise NotImplementedError(self.get_parent_map) 1059 1060 def _get_parent_map_no_fallbacks(self, revision_ids): 1061 """Same as Repository.get_parent_map except doesn't query fallbacks.""" 1062 # revisions index works in keys; this just works in revisions 1063 # therefore wrap and unwrap 1064 query_keys = [] 1065 result = {} 1066 for revision_id in revision_ids: 1067 if revision_id == _mod_revision.NULL_REVISION: 1068 result[revision_id] = () 1069 elif revision_id is None: 1070 raise ValueError('get_parent_map(None) is not valid') 1071 else: 1072 query_keys.append((revision_id,)) 1073 vf = self.revisions.without_fallbacks() 1074 for (revision_id,), parent_keys in ( 1075 vf.get_parent_map(query_keys).items()): 1076 if parent_keys: 1077 result[revision_id] = tuple([parent_revid 1078 for (parent_revid,) in parent_keys]) 1079 else: 1080 result[revision_id] = (_mod_revision.NULL_REVISION,) 1081 return result 1082 1083 def _make_parents_provider(self): 1084 if not self._format.supports_external_lookups: 1085 return self 1086 return graph.StackedParentsProvider(_LazyListJoin( 1087 [self._make_parents_provider_unstacked()], 1088 self._fallback_repositories)) 1089 1090 def _make_parents_provider_unstacked(self): 1091 return graph.CallableToParentsProviderAdapter( 1092 self._get_parent_map_no_fallbacks) 1093 1094 def get_known_graph_ancestry(self, revision_ids): 1095 """Return the known graph for a set of revision ids and their ancestors. 1096 """ 1097 raise NotImplementedError(self.get_known_graph_ancestry) 1098 1099 def get_file_graph(self): 1100 """Return the graph walker for files.""" 1101 raise NotImplementedError(self.get_file_graph) 1102 1103 def get_graph(self, other_repository=None): 1104 """Return the graph walker for this repository format""" 1105 parents_provider = self._make_parents_provider() 1106 if (other_repository is not None and 1107 not self.has_same_location(other_repository)): 1108 parents_provider = graph.StackedParentsProvider( 1109 [parents_provider, other_repository._make_parents_provider()]) 1110 return graph.Graph(parents_provider) 1111 1112 def set_make_working_trees(self, new_value): 1113 """Set the policy flag for making working trees when creating branches. 1114 1115 This only applies to branches that use this repository. 1116 1117 The default is 'True'. 1118 :param new_value: True to restore the default, False to disable making 1119 working trees. 1120 """ 1121 raise NotImplementedError(self.set_make_working_trees) 1122 1123 def make_working_trees(self): 1124 """Returns the policy for making working trees on new branches.""" 1125 raise NotImplementedError(self.make_working_trees) 1126 1127 def sign_revision(self, revision_id, gpg_strategy): 1128 raise NotImplementedError(self.sign_revision) 1129 1130 def verify_revision_signature(self, revision_id, gpg_strategy): 1131 """Verify the signature on a revision. 1132 1133 :param revision_id: the revision to verify 1134 :gpg_strategy: the GPGStrategy object to used 1135 1136 :return: gpg.SIGNATURE_VALID or a failed SIGNATURE_ value 1137 """ 1138 raise NotImplementedError(self.verify_revision_signature) 1139 1140 def verify_revision_signatures(self, revision_ids, gpg_strategy): 1141 """Verify revision signatures for a number of revisions. 1142 1143 :param revision_id: the revision to verify 1144 :gpg_strategy: the GPGStrategy object to used 1145 :return: Iterator over tuples with revision id, result and keys 1146 """ 1147 with self.lock_read(): 1148 for revid in revision_ids: 1149 (result, key) = self.verify_revision_signature(revid, gpg_strategy) 1150 yield revid, result, key 1151 1152 def has_signature_for_revision_id(self, revision_id): 1153 """Query for a revision signature for revision_id in the repository.""" 1154 raise NotImplementedError(self.has_signature_for_revision_id) 1155 1156 def get_signature_text(self, revision_id): 1157 """Return the text for a signature.""" 1158 raise NotImplementedError(self.get_signature_text) 1159 1160 def check(self, revision_ids=None, callback_refs=None, check_repo=True): 1161 """Check consistency of all history of given revision_ids. 1162 1163 Different repository implementations should override _check(). 1164 1165 :param revision_ids: A non-empty list of revision_ids whose ancestry 1166 will be checked. Typically the last revision_id of a branch. 1167 :param callback_refs: A dict of check-refs to resolve and callback 1168 the check/_check method on the items listed as wanting the ref. 1169 see breezy.check. 1170 :param check_repo: If False do not check the repository contents, just 1171 calculate the data callback_refs requires and call them back. 1172 """ 1173 return self._check(revision_ids=revision_ids, callback_refs=callback_refs, 1174 check_repo=check_repo) 1175 1176 def _check(self, revision_ids=None, callback_refs=None, check_repo=True): 1177 raise NotImplementedError(self.check) 1178 1179 def _warn_if_deprecated(self, branch=None): 1180 if not self._format.is_deprecated(): 1181 return 1182 global _deprecation_warning_done 1183 if _deprecation_warning_done: 1184 return 1185 try: 1186 if branch is None: 1187 conf = config.GlobalStack() 1188 else: 1189 conf = branch.get_config_stack() 1190 if 'format_deprecation' in conf.get('suppress_warnings'): 1191 return 1192 warning("Format %s for %s is deprecated -" 1193 " please use 'brz upgrade' to get better performance" 1194 % (self._format, self.controldir.transport.base)) 1195 finally: 1196 _deprecation_warning_done = True 1197 1198 def supports_rich_root(self): 1199 return self._format.rich_root_data 1200 1201 def _check_ascii_revisionid(self, revision_id, method): 1202 """Private helper for ascii-only repositories.""" 1203 # weave repositories refuse to store revisionids that are non-ascii. 1204 if revision_id is not None: 1205 # weaves require ascii revision ids. 1206 if isinstance(revision_id, str): 1207 try: 1208 revision_id.encode('ascii') 1209 except UnicodeEncodeError: 1210 raise errors.NonAsciiRevisionId(method, self) 1211 else: 1212 try: 1213 revision_id.decode('ascii') 1214 except UnicodeDecodeError: 1215 raise errors.NonAsciiRevisionId(method, self) 1216 1217 1218class RepositoryFormatRegistry(controldir.ControlComponentFormatRegistry): 1219 """Repository format registry.""" 1220 1221 def get_default(self): 1222 """Return the current default format.""" 1223 return controldir.format_registry.make_controldir('default').repository_format 1224 1225 1226network_format_registry = registry.FormatRegistry() 1227"""Registry of formats indexed by their network name. 1228 1229The network name for a repository format is an identifier that can be used when 1230referring to formats with smart server operations. See 1231RepositoryFormat.network_name() for more detail. 1232""" 1233 1234 1235format_registry = RepositoryFormatRegistry(network_format_registry) 1236"""Registry of formats, indexed by their BzrDirMetaFormat format string. 1237 1238This can contain either format instances themselves, or classes/factories that 1239can be called to obtain one. 1240""" 1241 1242 1243##################################################################### 1244# Repository Formats 1245 1246class RepositoryFormat(controldir.ControlComponentFormat): 1247 """A repository format. 1248 1249 Formats provide four things: 1250 * An initialization routine to construct repository data on disk. 1251 * a optional format string which is used when the BzrDir supports 1252 versioned children. 1253 * an open routine which returns a Repository instance. 1254 * A network name for referring to the format in smart server RPC 1255 methods. 1256 1257 There is one and only one Format subclass for each on-disk format. But 1258 there can be one Repository subclass that is used for several different 1259 formats. The _format attribute on a Repository instance can be used to 1260 determine the disk format. 1261 1262 Formats are placed in a registry by their format string for reference 1263 during opening. These should be subclasses of RepositoryFormat for 1264 consistency. 1265 1266 Once a format is deprecated, just deprecate the initialize and open 1267 methods on the format class. Do not deprecate the object, as the 1268 object may be created even when a repository instance hasn't been 1269 created. 1270 1271 Common instance attributes: 1272 _matchingcontroldir - the controldir format that the repository format was 1273 originally written to work with. This can be used if manually 1274 constructing a bzrdir and repository, or more commonly for test suite 1275 parameterization. 1276 """ 1277 1278 # Set to True or False in derived classes. True indicates that the format 1279 # supports ghosts gracefully. 1280 supports_ghosts = None 1281 # Can this repository be given external locations to lookup additional 1282 # data. Set to True or False in derived classes. 1283 supports_external_lookups = None 1284 # Does this format support CHK bytestring lookups. Set to True or False in 1285 # derived classes. 1286 supports_chks = None 1287 # Should fetch trigger a reconcile after the fetch? Only needed for 1288 # some repository formats that can suffer internal inconsistencies. 1289 _fetch_reconcile = False 1290 # Does this format have < O(tree_size) delta generation. Used to hint what 1291 # code path for commit, amongst other things. 1292 fast_deltas = None 1293 # Does doing a pack operation compress data? Useful for the pack UI command 1294 # (so if there is one pack, the operation can still proceed because it may 1295 # help), and for fetching when data won't have come from the same 1296 # compressor. 1297 pack_compresses = False 1298 # Does the repository storage understand references to trees? 1299 supports_tree_reference = None 1300 # Is the format experimental ? 1301 experimental = False 1302 # Does this repository format escape funky characters, or does it create 1303 # files with similar names as the versioned files in its contents on disk 1304 # ? 1305 supports_funky_characters = None 1306 # Does this repository format support leaving locks? 1307 supports_leaving_lock = None 1308 # Does this format support the full VersionedFiles interface? 1309 supports_full_versioned_files = None 1310 # Does this format support signing revision signatures? 1311 supports_revision_signatures = True 1312 # Can the revision graph have incorrect parents? 1313 revision_graph_can_have_wrong_parents = None 1314 # Does this format support setting revision ids? 1315 supports_setting_revision_ids = True 1316 # Does this format support rich root data? 1317 rich_root_data = None 1318 # Does this format support explicitly versioned directories? 1319 supports_versioned_directories = None 1320 # Can other repositories be nested into one of this format? 1321 supports_nesting_repositories = None 1322 # Is it possible for revisions to be present without being referenced 1323 # somewhere ? 1324 supports_unreferenced_revisions = None 1325 # Does this format store the current Branch.nick in a revision when 1326 # creating commits? 1327 supports_storing_branch_nick = True 1328 # Does the format support overriding the transport to use 1329 supports_overriding_transport = True 1330 # Does the format support setting custom revision properties? 1331 supports_custom_revision_properties = True 1332 # Does the format record per-file revision metadata? 1333 records_per_file_revision = True 1334 supports_multiple_authors = True 1335 1336 def __repr__(self): 1337 return "%s()" % self.__class__.__name__ 1338 1339 def __eq__(self, other): 1340 # format objects are generally stateless 1341 return isinstance(other, self.__class__) 1342 1343 def __ne__(self, other): 1344 return not self == other 1345 1346 def get_format_description(self): 1347 """Return the short description for this format.""" 1348 raise NotImplementedError(self.get_format_description) 1349 1350 def initialize(self, controldir, shared=False): 1351 """Initialize a repository of this format in controldir. 1352 1353 :param controldir: The controldir to put the new repository in it. 1354 :param shared: The repository should be initialized as a sharable one. 1355 :returns: The new repository object. 1356 1357 This may raise UninitializableFormat if shared repository are not 1358 compatible the controldir. 1359 """ 1360 raise NotImplementedError(self.initialize) 1361 1362 def is_supported(self): 1363 """Is this format supported? 1364 1365 Supported formats must be initializable and openable. 1366 Unsupported formats may not support initialization or committing or 1367 some other features depending on the reason for not being supported. 1368 """ 1369 return True 1370 1371 def is_deprecated(self): 1372 """Is this format deprecated? 1373 1374 Deprecated formats may trigger a user-visible warning recommending 1375 the user to upgrade. They are still fully supported. 1376 """ 1377 return False 1378 1379 def network_name(self): 1380 """A simple byte string uniquely identifying this format for RPC calls. 1381 1382 MetaDir repository formats use their disk format string to identify the 1383 repository over the wire. All in one formats such as bzr < 0.8, and 1384 foreign formats like svn/git and hg should use some marker which is 1385 unique and immutable. 1386 """ 1387 raise NotImplementedError(self.network_name) 1388 1389 def check_conversion_target(self, target_format): 1390 if self.rich_root_data and not target_format.rich_root_data: 1391 raise errors.BadConversionTarget( 1392 'Does not support rich root data.', target_format, 1393 from_format=self) 1394 if (self.supports_tree_reference 1395 and not getattr(target_format, 'supports_tree_reference', False)): 1396 raise errors.BadConversionTarget( 1397 'Does not support nested trees', target_format, 1398 from_format=self) 1399 1400 def open(self, controldir, _found=False): 1401 """Return an instance of this format for a controldir. 1402 1403 _found is a private parameter, do not use it. 1404 """ 1405 raise NotImplementedError(self.open) 1406 1407 def _run_post_repo_init_hooks(self, repository, controldir, shared): 1408 from .controldir import ControlDir, RepoInitHookParams 1409 hooks = ControlDir.hooks['post_repo_init'] 1410 if not hooks: 1411 return 1412 params = RepoInitHookParams(repository, self, controldir, shared) 1413 for hook in hooks: 1414 hook(params) 1415 1416 1417# formats which have no format string are not discoverable or independently 1418# creatable on disk, so are not registered in format_registry. They're 1419# all in breezy.bzr.knitreponow. When an instance of one of these is 1420# needed, it's constructed directly by the ControlDir. Non-native formats where 1421# the repository is not separately opened are similar. 1422 1423format_registry.register_lazy( 1424 b'Bazaar-NG Knit Repository Format 1', 1425 'breezy.bzr.knitrepo', 1426 'RepositoryFormatKnit1', 1427 ) 1428 1429format_registry.register_lazy( 1430 b'Bazaar Knit Repository Format 3 (bzr 0.15)\n', 1431 'breezy.bzr.knitrepo', 1432 'RepositoryFormatKnit3', 1433 ) 1434 1435format_registry.register_lazy( 1436 b'Bazaar Knit Repository Format 4 (bzr 1.0)\n', 1437 'breezy.bzr.knitrepo', 1438 'RepositoryFormatKnit4', 1439 ) 1440 1441# Pack-based formats. There is one format for pre-subtrees, and one for 1442# post-subtrees to allow ease of testing. 1443# NOTE: These are experimental in 0.92. Stable in 1.0 and above 1444format_registry.register_lazy( 1445 b'Bazaar pack repository format 1 (needs bzr 0.92)\n', 1446 'breezy.bzr.knitpack_repo', 1447 'RepositoryFormatKnitPack1', 1448 ) 1449format_registry.register_lazy( 1450 b'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n', 1451 'breezy.bzr.knitpack_repo', 1452 'RepositoryFormatKnitPack3', 1453 ) 1454format_registry.register_lazy( 1455 b'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n', 1456 'breezy.bzr.knitpack_repo', 1457 'RepositoryFormatKnitPack4', 1458 ) 1459format_registry.register_lazy( 1460 b'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n', 1461 'breezy.bzr.knitpack_repo', 1462 'RepositoryFormatKnitPack5', 1463 ) 1464format_registry.register_lazy( 1465 b'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n', 1466 'breezy.bzr.knitpack_repo', 1467 'RepositoryFormatKnitPack5RichRoot', 1468 ) 1469format_registry.register_lazy( 1470 b'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n', 1471 'breezy.bzr.knitpack_repo', 1472 'RepositoryFormatKnitPack5RichRootBroken', 1473 ) 1474format_registry.register_lazy( 1475 b'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n', 1476 'breezy.bzr.knitpack_repo', 1477 'RepositoryFormatKnitPack6', 1478 ) 1479format_registry.register_lazy( 1480 b'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n', 1481 'breezy.bzr.knitpack_repo', 1482 'RepositoryFormatKnitPack6RichRoot', 1483 ) 1484format_registry.register_lazy( 1485 b'Bazaar repository format 2a (needs bzr 1.16 or later)\n', 1486 'breezy.bzr.groupcompress_repo', 1487 'RepositoryFormat2a', 1488 ) 1489 1490# Development formats. 1491# Check their docstrings to see if/when they are obsolete. 1492format_registry.register_lazy( 1493 (b"Bazaar development format 2 with subtree support " 1494 b"(needs bzr.dev from before 1.8)\n"), 1495 'breezy.bzr.knitpack_repo', 1496 'RepositoryFormatPackDevelopment2Subtree', 1497 ) 1498format_registry.register_lazy( 1499 b'Bazaar development format 8\n', 1500 'breezy.bzr.groupcompress_repo', 1501 'RepositoryFormat2aSubtree', 1502 ) 1503 1504 1505class InterRepository(InterObject): 1506 """This class represents operations taking place between two repositories. 1507 1508 Its instances have methods like copy_content and fetch, and contain 1509 references to the source and target repositories these operations can be 1510 carried out on. 1511 1512 Often we will provide convenience methods on 'repository' which carry out 1513 operations with another repository - they will always forward to 1514 InterRepository.get(other).method_name(parameters). 1515 """ 1516 1517 _optimisers = [] 1518 """The available optimised InterRepository types.""" 1519 1520 def copy_content(self, revision_id=None): 1521 """Make a complete copy of the content in self into destination. 1522 1523 This is a destructive operation! Do not use it on existing 1524 repositories. 1525 1526 :param revision_id: Only copy the content needed to construct 1527 revision_id and its parents. 1528 """ 1529 with self.lock_write(): 1530 try: 1531 self.target.set_make_working_trees( 1532 self.source.make_working_trees()) 1533 except (NotImplementedError, errors.RepositoryUpgradeRequired): 1534 pass 1535 self.target.fetch(self.source, revision_id=revision_id) 1536 1537 def fetch(self, revision_id=None, find_ghosts=False, lossy=False): 1538 """Fetch the content required to construct revision_id. 1539 1540 The content is copied from self.source to self.target. 1541 1542 :param revision_id: if None all content is copied, if NULL_REVISION no 1543 content is copied. 1544 :return: FetchResult 1545 """ 1546 raise NotImplementedError(self.fetch) 1547 1548 def search_missing_revision_ids( 1549 self, find_ghosts=True, revision_ids=None, if_present_ids=None, 1550 limit=None): 1551 """Return the revision ids that source has that target does not. 1552 1553 :param revision_ids: return revision ids included by these 1554 revision_ids. NoSuchRevision will be raised if any of these 1555 revisions are not present. 1556 :param if_present_ids: like revision_ids, but will not cause 1557 NoSuchRevision if any of these are absent, instead they will simply 1558 not be in the result. This is useful for e.g. finding revisions 1559 to fetch for tags, which may reference absent revisions. 1560 :param find_ghosts: If True find missing revisions in deep history 1561 rather than just finding the surface difference. 1562 :param limit: Maximum number of revisions to return, topologically 1563 ordered 1564 :return: A breezy.graph.SearchResult. 1565 """ 1566 raise NotImplementedError(self.search_missing_revision_ids) 1567 1568 @staticmethod 1569 def _same_model(source, target): 1570 """True if source and target have the same data representation. 1571 1572 Note: this is always called on the base class; overriding it in a 1573 subclass will have no effect. 1574 """ 1575 try: 1576 InterRepository._assert_same_model(source, target) 1577 return True 1578 except errors.IncompatibleRepositories as e: 1579 return False 1580 1581 @staticmethod 1582 def _assert_same_model(source, target): 1583 """Raise an exception if two repositories do not use the same model. 1584 """ 1585 if source.supports_rich_root() != target.supports_rich_root(): 1586 raise errors.IncompatibleRepositories(source, target, 1587 "different rich-root support") 1588 if source._serializer != target._serializer: 1589 raise errors.IncompatibleRepositories(source, target, 1590 "different serializers") 1591 1592 1593class CopyConverter(object): 1594 """A repository conversion tool which just performs a copy of the content. 1595 1596 This is slow but quite reliable. 1597 """ 1598 1599 def __init__(self, target_format): 1600 """Create a CopyConverter. 1601 1602 :param target_format: The format the resulting repository should be. 1603 """ 1604 self.target_format = target_format 1605 1606 def convert(self, repo, pb): 1607 """Perform the conversion of to_convert, giving feedback via pb. 1608 1609 :param to_convert: The disk object to convert. 1610 :param pb: a progress bar to use for progress information. 1611 """ 1612 with ui.ui_factory.nested_progress_bar() as pb: 1613 self.count = 0 1614 self.total = 4 1615 # this is only useful with metadir layouts - separated repo content. 1616 # trigger an assertion if not such 1617 repo._format.get_format_string() 1618 self.repo_dir = repo.controldir 1619 pb.update(gettext('Moving repository to repository.backup')) 1620 self.repo_dir.transport.move('repository', 'repository.backup') 1621 backup_transport = self.repo_dir.transport.clone( 1622 'repository.backup') 1623 repo._format.check_conversion_target(self.target_format) 1624 self.source_repo = repo._format.open(self.repo_dir, 1625 _found=True, 1626 _override_transport=backup_transport) 1627 pb.update(gettext('Creating new repository')) 1628 converted = self.target_format.initialize(self.repo_dir, 1629 self.source_repo.is_shared()) 1630 with converted.lock_write(): 1631 pb.update(gettext('Copying content')) 1632 self.source_repo.copy_content_into(converted) 1633 pb.update(gettext('Deleting old repository content')) 1634 self.repo_dir.transport.delete_tree('repository.backup') 1635 ui.ui_factory.note(gettext('repository converted')) 1636 1637 1638def _strip_NULL_ghosts(revision_graph): 1639 """Also don't use this. more compatibility code for unmigrated clients.""" 1640 # Filter ghosts, and null: 1641 if _mod_revision.NULL_REVISION in revision_graph: 1642 del revision_graph[_mod_revision.NULL_REVISION] 1643 for key, parents in revision_graph.items(): 1644 revision_graph[key] = tuple(parent for parent in parents if parent 1645 in revision_graph) 1646 return revision_graph 1647 1648 1649def _iter_for_revno(repo, partial_history_cache, stop_index=None, 1650 stop_revision=None): 1651 """Extend the partial history to include a given index 1652 1653 If a stop_index is supplied, stop when that index has been reached. 1654 If a stop_revision is supplied, stop when that revision is 1655 encountered. Otherwise, stop when the beginning of history is 1656 reached. 1657 1658 :param stop_index: The index which should be present. When it is 1659 present, history extension will stop. 1660 :param stop_revision: The revision id which should be present. When 1661 it is encountered, history extension will stop. 1662 """ 1663 start_revision = partial_history_cache[-1] 1664 graph = repo.get_graph() 1665 iterator = graph.iter_lefthand_ancestry(start_revision, 1666 (_mod_revision.NULL_REVISION,)) 1667 try: 1668 # skip the last revision in the list 1669 next(iterator) 1670 while True: 1671 if (stop_index is not None and 1672 len(partial_history_cache) > stop_index): 1673 break 1674 if partial_history_cache[-1] == stop_revision: 1675 break 1676 revision_id = next(iterator) 1677 partial_history_cache.append(revision_id) 1678 except StopIteration: 1679 # No more history 1680 return 1681 1682 1683class _LazyListJoin(object): 1684 """An iterable yielding the contents of many lists as one list. 1685 1686 Each iterator made from this will reflect the current contents of the lists 1687 at the time the iterator is made. 1688 1689 This is used by Repository's _make_parents_provider implementation so that 1690 it is safe to do:: 1691 1692 pp = repo._make_parents_provider() # uses a list of fallback repos 1693 pp.add_fallback_repository(other_repo) # appends to that list 1694 result = pp.get_parent_map(...) 1695 # The result will include revs from other_repo 1696 """ 1697 1698 def __init__(self, *list_parts): 1699 self.list_parts = list_parts 1700 1701 def __iter__(self): 1702 full_list = [] 1703 for list_part in self.list_parts: 1704 full_list.extend(list_part) 1705 return iter(full_list) 1706 1707 def __repr__(self): 1708 return "%s.%s(%s)" % (self.__module__, self.__class__.__name__, 1709 self.list_parts) 1710