/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: John Arbash Meinel
  • Date: 2009-07-08 14:37:25 UTC
  • mfrom: (4516 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4517.
  • Revision ID: john@arbash-meinel.com-20090708143725-sc9sjy3mz4cxwxzz
Merge bzr.dev 4516

Show diffs side-by-side

added added

removed removed

Lines of Context:
57
57
    entry_factory,
58
58
    )
59
59
from bzrlib import registry
60
 
from bzrlib.symbol_versioning import (
61
 
        deprecated_method,
62
 
        )
63
60
from bzrlib.trace import (
64
61
    log_exception_quietly, note, mutter, mutter_callsite, warning)
65
62
 
494
491
            ie.executable = content_summary[2]
495
492
            file_obj, stat_value = tree.get_file_with_stat(ie.file_id, path)
496
493
            try:
497
 
                lines = file_obj.readlines()
 
494
                text = file_obj.read()
498
495
            finally:
499
496
                file_obj.close()
500
497
            try:
501
498
                ie.text_sha1, ie.text_size = self._add_text_to_weave(
502
 
                    ie.file_id, lines, heads, nostore_sha)
 
499
                    ie.file_id, text, heads, nostore_sha)
503
500
                # Let the caller know we generated a stat fingerprint.
504
501
                fingerprint = (ie.text_sha1, stat_value)
505
502
            except errors.ExistingContent:
517
514
                # carry over:
518
515
                ie.revision = parent_entry.revision
519
516
                return self._get_delta(ie, basis_inv, path), False, None
520
 
            lines = []
521
 
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
517
            self._add_text_to_weave(ie.file_id, '', heads, None)
522
518
        elif kind == 'symlink':
523
519
            current_link_target = content_summary[3]
524
520
            if not store:
532
528
                ie.symlink_target = parent_entry.symlink_target
533
529
                return self._get_delta(ie, basis_inv, path), False, None
534
530
            ie.symlink_target = current_link_target
535
 
            lines = []
536
 
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
531
            self._add_text_to_weave(ie.file_id, '', heads, None)
537
532
        elif kind == 'tree-reference':
538
533
            if not store:
539
534
                if content_summary[3] != parent_entry.reference_revision:
544
539
                ie.revision = parent_entry.revision
545
540
                return self._get_delta(ie, basis_inv, path), False, None
546
541
            ie.reference_revision = content_summary[3]
547
 
            lines = []
548
 
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
542
            self._add_text_to_weave(ie.file_id, '', heads, None)
549
543
        else:
550
544
            raise NotImplementedError('unknown kind')
551
545
        ie.revision = self._new_revision_id
745
739
                        entry.executable = True
746
740
                    else:
747
741
                        entry.executable = False
748
 
                    if (carry_over_possible and 
 
742
                    if (carry_over_possible and
749
743
                        parent_entry.executable == entry.executable):
750
744
                            # Check the file length, content hash after reading
751
745
                            # the file.
754
748
                        nostore_sha = None
755
749
                    file_obj, stat_value = tree.get_file_with_stat(file_id, change[1][1])
756
750
                    try:
757
 
                        lines = file_obj.readlines()
 
751
                        text = file_obj.read()
758
752
                    finally:
759
753
                        file_obj.close()
760
754
                    try:
761
755
                        entry.text_sha1, entry.text_size = self._add_text_to_weave(
762
 
                            file_id, lines, heads, nostore_sha)
 
756
                            file_id, text, heads, nostore_sha)
763
757
                        yield file_id, change[1][1], (entry.text_sha1, stat_value)
764
758
                    except errors.ExistingContent:
765
759
                        # No content change against a carry_over parent
774
768
                        parent_entry.symlink_target == entry.symlink_target):
775
769
                        carried_over = True
776
770
                    else:
777
 
                        self._add_text_to_weave(change[0], [], heads, None)
 
771
                        self._add_text_to_weave(change[0], '', heads, None)
778
772
                elif kind == 'directory':
779
773
                    if carry_over_possible:
780
774
                        carried_over = True
782
776
                        # Nothing to set on the entry.
783
777
                        # XXX: split into the Root and nonRoot versions.
784
778
                        if change[1][1] != '' or self.repository.supports_rich_root():
785
 
                            self._add_text_to_weave(change[0], [], heads, None)
 
779
                            self._add_text_to_weave(change[0], '', heads, None)
786
780
                elif kind == 'tree-reference':
787
781
                    if not self.repository._format.supports_tree_reference:
788
782
                        # This isn't quite sane as an error, but we shouldn't
791
785
                        # references.
792
786
                        raise errors.UnsupportedOperation(tree.add_reference,
793
787
                            self.repository)
794
 
                    entry.reference_revision = \
795
 
                        tree.get_reference_revision(change[0])
 
788
                    reference_revision = tree.get_reference_revision(change[0])
 
789
                    entry.reference_revision = reference_revision
796
790
                    if (carry_over_possible and
797
791
                        parent_entry.reference_revision == reference_revision):
798
792
                        carried_over = True
799
793
                    else:
800
 
                        self._add_text_to_weave(change[0], [], heads, None)
 
794
                        self._add_text_to_weave(change[0], '', heads, None)
801
795
                else:
802
796
                    raise AssertionError('unknown kind %r' % kind)
803
797
                if not carried_over:
818
812
            self._require_root_change(tree)
819
813
        self.basis_delta_revision = basis_revision_id
820
814
 
821
 
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
822
 
        # Note: as we read the content directly from the tree, we know its not
823
 
        # been turned into unicode or badly split - but a broken tree
824
 
        # implementation could give us bad output from readlines() so this is
825
 
        # not a guarantee of safety. What would be better is always checking
826
 
        # the content during test suite execution. RBC 20070912
827
 
        parent_keys = tuple((file_id, parent) for parent in parents)
828
 
        return self.repository.texts.add_lines(
829
 
            (file_id, self._new_revision_id), parent_keys, new_lines,
830
 
            nostore_sha=nostore_sha, random_id=self.random_revid,
831
 
            check_content=False)[0:2]
 
815
    def _add_text_to_weave(self, file_id, new_text, parents, nostore_sha):
 
816
        parent_keys = tuple([(file_id, parent) for parent in parents])
 
817
        return self.repository.texts._add_text(
 
818
            (file_id, self._new_revision_id), parent_keys, new_text,
 
819
            nostore_sha=nostore_sha, random_id=self.random_revid)[0:2]
832
820
 
833
821
 
834
822
class RootCommitBuilder(CommitBuilder):
969
957
        """
970
958
        if not self._format.supports_external_lookups:
971
959
            raise errors.UnstackableRepositoryFormat(self._format, self.base)
 
960
        if self.is_locked():
 
961
            # This repository will call fallback.unlock() when we transition to
 
962
            # the unlocked state, so we make sure to increment the lock count
 
963
            repository.lock_read()
972
964
        self._check_fallback_repository(repository)
973
965
        self._fallback_repositories.append(repository)
974
966
        self.texts.add_fallback_versioned_files(repository.texts)
1023
1015
                               parents, basis_inv=None, propagate_caches=False):
1024
1016
        """Add a new inventory expressed as a delta against another revision.
1025
1017
 
 
1018
        See the inventory developers documentation for the theory behind
 
1019
        inventory deltas.
 
1020
 
1026
1021
        :param basis_revision_id: The inventory id the delta was created
1027
1022
            against. (This does not have to be a direct parent.)
1028
1023
        :param delta: The inventory delta (see Inventory.apply_delta for
1240
1235
        """
1241
1236
        locked = self.is_locked()
1242
1237
        result = self.control_files.lock_write(token=token)
1243
 
        for repo in self._fallback_repositories:
1244
 
            # Writes don't affect fallback repos
1245
 
            repo.lock_read()
1246
1238
        if not locked:
 
1239
            for repo in self._fallback_repositories:
 
1240
                # Writes don't affect fallback repos
 
1241
                repo.lock_read()
1247
1242
            self._refresh_data()
1248
1243
        return result
1249
1244
 
1250
1245
    def lock_read(self):
1251
1246
        locked = self.is_locked()
1252
1247
        self.control_files.lock_read()
1253
 
        for repo in self._fallback_repositories:
1254
 
            repo.lock_read()
1255
1248
        if not locked:
 
1249
            for repo in self._fallback_repositories:
 
1250
                repo.lock_read()
1256
1251
            self._refresh_data()
1257
1252
 
1258
1253
    def get_physical_lock_status(self):
1409
1404
            raise errors.BzrError('mismatched lock context %r and '
1410
1405
                'write group %r.' %
1411
1406
                (self.get_transaction(), self._write_group))
1412
 
        self._commit_write_group()
 
1407
        result = self._commit_write_group()
1413
1408
        self._write_group = None
 
1409
        return result
1414
1410
 
1415
1411
    def _commit_write_group(self):
1416
1412
        """Template method for per-repository write group cleanup.
1424
1420
    def suspend_write_group(self):
1425
1421
        raise errors.UnsuspendableWriteGroup(self)
1426
1422
 
1427
 
    def get_missing_parent_inventories(self):
 
1423
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
1428
1424
        """Return the keys of missing inventory parents for revisions added in
1429
1425
        this write group.
1430
1426
 
1439
1435
            return set()
1440
1436
        if not self.is_in_write_group():
1441
1437
            raise AssertionError('not in a write group')
1442
 
                
 
1438
 
1443
1439
        # XXX: We assume that every added revision already has its
1444
1440
        # corresponding inventory, so we only check for parent inventories that
1445
1441
        # might be missing, rather than all inventories.
1448
1444
        unstacked_inventories = self.inventories._index
1449
1445
        present_inventories = unstacked_inventories.get_parent_map(
1450
1446
            key[-1:] for key in parents)
1451
 
        if len(parents.difference(present_inventories)) == 0:
 
1447
        parents.difference_update(present_inventories)
 
1448
        if len(parents) == 0:
1452
1449
            # No missing parent inventories.
1453
1450
            return set()
 
1451
        if not check_for_missing_texts:
 
1452
            return set(('inventories', rev_id) for (rev_id,) in parents)
1454
1453
        # Ok, now we have a list of missing inventories.  But these only matter
1455
1454
        # if the inventories that reference them are missing some texts they
1456
1455
        # appear to introduce.
1577
1576
        self.control_files.unlock()
1578
1577
        if self.control_files._lock_count == 0:
1579
1578
            self._inventory_entry_cache.clear()
1580
 
        for repo in self._fallback_repositories:
1581
 
            repo.unlock()
 
1579
            for repo in self._fallback_repositories:
 
1580
                repo.unlock()
1582
1581
 
1583
1582
    @needs_read_lock
1584
1583
    def clone(self, a_bzrdir, revision_id=None):
1912
1911
                    yield line, revid
1913
1912
 
1914
1913
    def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
1915
 
        revision_ids):
 
1914
        revision_keys):
1916
1915
        """Helper routine for fileids_altered_by_revision_ids.
1917
1916
 
1918
1917
        This performs the translation of xml lines to revision ids.
1919
1918
 
1920
1919
        :param line_iterator: An iterator of lines, origin_version_id
1921
 
        :param revision_ids: The revision ids to filter for. This should be a
 
1920
        :param revision_keys: The revision ids to filter for. This should be a
1922
1921
            set or other type which supports efficient __contains__ lookups, as
1923
 
            the revision id from each parsed line will be looked up in the
1924
 
            revision_ids filter.
 
1922
            the revision key from each parsed line will be looked up in the
 
1923
            revision_keys filter.
1925
1924
        :return: a dictionary mapping altered file-ids to an iterable of
1926
1925
        revision_ids. Each altered file-ids has the exact revision_ids that
1927
1926
        altered it listed explicitly.
1928
1927
        """
1929
1928
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
1930
1929
                line_iterator).iterkeys())
1931
 
        # Note that revision_ids are revision keys.
1932
 
        parent_maps = self.revisions.get_parent_map(revision_ids)
1933
 
        parents = set()
1934
 
        map(parents.update, parent_maps.itervalues())
1935
 
        parents.difference_update(revision_ids)
 
1930
        parent_keys = self._find_parent_keys_of_revisions(revision_keys)
1936
1931
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
1937
 
            self._inventory_xml_lines_for_keys(parents)))
 
1932
            self._inventory_xml_lines_for_keys(parent_keys)))
1938
1933
        new_keys = seen - parent_seen
1939
1934
        result = {}
1940
1935
        setdefault = result.setdefault
1942
1937
            setdefault(key[0], set()).add(key[-1])
1943
1938
        return result
1944
1939
 
 
1940
    def _find_parent_ids_of_revisions(self, revision_ids):
 
1941
        """Find all parent ids that are mentioned in the revision graph.
 
1942
 
 
1943
        :return: set of revisions that are parents of revision_ids which are
 
1944
            not part of revision_ids themselves
 
1945
        """
 
1946
        parent_map = self.get_parent_map(revision_ids)
 
1947
        parent_ids = set()
 
1948
        map(parent_ids.update, parent_map.itervalues())
 
1949
        parent_ids.difference_update(revision_ids)
 
1950
        parent_ids.discard(_mod_revision.NULL_REVISION)
 
1951
        return parent_ids
 
1952
 
 
1953
    def _find_parent_keys_of_revisions(self, revision_keys):
 
1954
        """Similar to _find_parent_ids_of_revisions, but used with keys.
 
1955
 
 
1956
        :param revision_keys: An iterable of revision_keys.
 
1957
        :return: The parents of all revision_keys that are not already in
 
1958
            revision_keys
 
1959
        """
 
1960
        parent_map = self.revisions.get_parent_map(revision_keys)
 
1961
        parent_keys = set()
 
1962
        map(parent_keys.update, parent_map.itervalues())
 
1963
        parent_keys.difference_update(revision_keys)
 
1964
        parent_keys.discard(_mod_revision.NULL_REVISION)
 
1965
        return parent_keys
 
1966
 
1945
1967
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
1946
1968
        """Find the file ids and versions affected by revisions.
1947
1969
 
2227
2249
        """
2228
2250
        return self.get_revision(revision_id).inventory_sha1
2229
2251
 
 
2252
    def get_rev_id_for_revno(self, revno, known_pair):
 
2253
        """Return the revision id of a revno, given a later (revno, revid)
 
2254
        pair in the same history.
 
2255
 
 
2256
        :return: if found (True, revid).  If the available history ran out
 
2257
            before reaching the revno, then this returns
 
2258
            (False, (closest_revno, closest_revid)).
 
2259
        """
 
2260
        known_revno, known_revid = known_pair
 
2261
        partial_history = [known_revid]
 
2262
        distance_from_known = known_revno - revno
 
2263
        if distance_from_known < 0:
 
2264
            raise ValueError(
 
2265
                'requested revno (%d) is later than given known revno (%d)'
 
2266
                % (revno, known_revno))
 
2267
        try:
 
2268
            _iter_for_revno(
 
2269
                self, partial_history, stop_index=distance_from_known)
 
2270
        except errors.RevisionNotPresent, err:
 
2271
            if err.revision_id == known_revid:
 
2272
                # The start revision (known_revid) wasn't found.
 
2273
                raise
 
2274
            # This is a stacked repository with no fallbacks, or a there's a
 
2275
            # left-hand ghost.  Either way, even though the revision named in
 
2276
            # the error isn't in this repo, we know it's the next step in this
 
2277
            # left-hand history.
 
2278
            partial_history.append(err.revision_id)
 
2279
        if len(partial_history) <= distance_from_known:
 
2280
            # Didn't find enough history to get a revid for the revno.
 
2281
            earliest_revno = known_revno - len(partial_history) + 1
 
2282
            return (False, (earliest_revno, partial_history[-1]))
 
2283
        if len(partial_history) - 1 > distance_from_known:
 
2284
            raise AssertionError('_iter_for_revno returned too much history')
 
2285
        return (True, partial_history[-1])
 
2286
 
2230
2287
    def iter_reverse_revision_history(self, revision_id):
2231
2288
        """Iterate backwards through revision ids in the lefthand history
2232
2289
 
2238
2295
        while True:
2239
2296
            if next_id in (None, _mod_revision.NULL_REVISION):
2240
2297
                return
 
2298
            try:
 
2299
                parents = graph.get_parent_map([next_id])[next_id]
 
2300
            except KeyError:
 
2301
                raise errors.RevisionNotPresent(next_id, self)
2241
2302
            yield next_id
2242
 
            # Note: The following line may raise KeyError in the event of
2243
 
            # truncated history. We decided not to have a try:except:raise
2244
 
            # RevisionNotPresent here until we see a use for it, because of the
2245
 
            # cost in an inner loop that is by its very nature O(history).
2246
 
            # Robert Collins 20080326
2247
 
            parents = graph.get_parent_map([next_id])[next_id]
2248
2303
            if len(parents) == 0:
2249
2304
                return
2250
2305
            else:
2364
2419
            keys = tsort.topo_sort(parent_map)
2365
2420
        return [None] + list(keys)
2366
2421
 
2367
 
    def pack(self):
 
2422
    def pack(self, hint=None):
2368
2423
        """Compress the data within the repository.
2369
2424
 
2370
2425
        This operation only makes sense for some repository types. For other
2373
2428
        This stub method does not require a lock, but subclasses should use
2374
2429
        @needs_write_lock as this is a long running call its reasonable to
2375
2430
        implicitly lock for the user.
 
2431
 
 
2432
        :param hint: If not supplied, the whole repository is packed.
 
2433
            If supplied, the repository may use the hint parameter as a
 
2434
            hint for the parts of the repository to pack. A hint can be
 
2435
            obtained from the result of commit_write_group(). Out of
 
2436
            date hints are simply ignored, because concurrent operations
 
2437
            can obsolete them rapidly.
2376
2438
        """
2377
2439
 
2378
2440
    def get_transaction(self):
2379
2441
        return self.control_files.get_transaction()
2380
2442
 
2381
2443
    def get_parent_map(self, revision_ids):
2382
 
        """See graph._StackedParentsProvider.get_parent_map"""
 
2444
        """See graph.StackedParentsProvider.get_parent_map"""
2383
2445
        # revisions index works in keys; this just works in revisions
2384
2446
        # therefore wrap and unwrap
2385
2447
        query_keys = []
2408
2470
        parents_provider = self._make_parents_provider()
2409
2471
        if (other_repository is not None and
2410
2472
            not self.has_same_location(other_repository)):
2411
 
            parents_provider = graph._StackedParentsProvider(
 
2473
            parents_provider = graph.StackedParentsProvider(
2412
2474
                [parents_provider, other_repository._make_parents_provider()])
2413
2475
        return graph.Graph(parents_provider)
2414
2476
 
2781
2843
    # Does this format have < O(tree_size) delta generation. Used to hint what
2782
2844
    # code path for commit, amongst other things.
2783
2845
    fast_deltas = None
 
2846
    # Does doing a pack operation compress data? Useful for the pack UI command
 
2847
    # (so if there is one pack, the operation can still proceed because it may
 
2848
    # help), and for fetching when data won't have come from the same
 
2849
    # compressor.
 
2850
    pack_compresses = False
2784
2851
 
2785
2852
    def __str__(self):
2786
2853
        return "<%s>" % self.__class__.__name__
3055
3122
    'RepositoryFormatCHK1',
3056
3123
    )
3057
3124
 
 
3125
format_registry.register_lazy(
 
3126
    'Bazaar development format - chk repository with bencode revision '
 
3127
        'serialization (needs bzr.dev from 1.16)\n',
 
3128
    'bzrlib.repofmt.groupcompress_repo',
 
3129
    'RepositoryFormatCHK2',
 
3130
    )
 
3131
format_registry.register_lazy(
 
3132
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
3133
    'bzrlib.repofmt.groupcompress_repo',
 
3134
    'RepositoryFormat2a',
 
3135
    )
 
3136
 
3058
3137
 
3059
3138
class InterRepository(InterObject):
3060
3139
    """This class represents operations taking place between two repositories.
3401
3480
        return self.source.revision_ids_to_search_result(result_set)
3402
3481
 
3403
3482
 
3404
 
class InterPackRepo(InterSameDataRepository):
3405
 
    """Optimised code paths between Pack based repositories."""
3406
 
 
3407
 
    @classmethod
3408
 
    def _get_repo_format_to_test(self):
3409
 
        from bzrlib.repofmt import pack_repo
3410
 
        return pack_repo.RepositoryFormatKnitPack6RichRoot()
3411
 
 
3412
 
    @staticmethod
3413
 
    def is_compatible(source, target):
3414
 
        """Be compatible with known Pack formats.
3415
 
 
3416
 
        We don't test for the stores being of specific types because that
3417
 
        could lead to confusing results, and there is no need to be
3418
 
        overly general.
3419
 
 
3420
 
        InterPackRepo does not support CHK based repositories.
3421
 
        """
3422
 
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
3423
 
        from bzrlib.repofmt.groupcompress_repo import RepositoryFormatCHK1
3424
 
        try:
3425
 
            are_packs = (isinstance(source._format, RepositoryFormatPack) and
3426
 
                isinstance(target._format, RepositoryFormatPack))
3427
 
            not_packs = (isinstance(source._format, RepositoryFormatCHK1) or
3428
 
                isinstance(target._format, RepositoryFormatCHK1))
3429
 
        except AttributeError:
3430
 
            return False
3431
 
        if not_packs or not are_packs:
3432
 
            return False
3433
 
        return InterRepository._same_model(source, target)
3434
 
 
3435
 
    @needs_write_lock
3436
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3437
 
            fetch_spec=None):
3438
 
        """See InterRepository.fetch()."""
3439
 
        if (len(self.source._fallback_repositories) > 0 or
3440
 
            len(self.target._fallback_repositories) > 0):
3441
 
            # The pack layer is not aware of fallback repositories, so when
3442
 
            # fetching from a stacked repository or into a stacked repository
3443
 
            # we use the generic fetch logic which uses the VersionedFiles
3444
 
            # attributes on repository.
3445
 
            from bzrlib.fetch import RepoFetcher
3446
 
            fetcher = RepoFetcher(self.target, self.source, revision_id,
3447
 
                    pb, find_ghosts, fetch_spec=fetch_spec)
3448
 
        if fetch_spec is not None:
3449
 
            if len(list(fetch_spec.heads)) != 1:
3450
 
                raise AssertionError(
3451
 
                    "InterPackRepo.fetch doesn't support "
3452
 
                    "fetching multiple heads yet.")
3453
 
            revision_id = list(fetch_spec.heads)[0]
3454
 
            fetch_spec = None
3455
 
        if revision_id is None:
3456
 
            # TODO:
3457
 
            # everything to do - use pack logic
3458
 
            # to fetch from all packs to one without
3459
 
            # inventory parsing etc, IFF nothing to be copied is in the target.
3460
 
            # till then:
3461
 
            source_revision_ids = frozenset(self.source.all_revision_ids())
3462
 
            revision_ids = source_revision_ids - \
3463
 
                frozenset(self.target.get_parent_map(source_revision_ids))
3464
 
            revision_keys = [(revid,) for revid in revision_ids]
3465
 
            index = self.target._pack_collection.revision_index.combined_index
3466
 
            present_revision_ids = set(item[1][0] for item in
3467
 
                index.iter_entries(revision_keys))
3468
 
            revision_ids = set(revision_ids) - present_revision_ids
3469
 
            # implementing the TODO will involve:
3470
 
            # - detecting when all of a pack is selected
3471
 
            # - avoiding as much as possible pre-selection, so the
3472
 
            # more-core routines such as create_pack_from_packs can filter in
3473
 
            # a just-in-time fashion. (though having a HEADS list on a
3474
 
            # repository might make this a lot easier, because we could
3475
 
            # sensibly detect 'new revisions' without doing a full index scan.
3476
 
        elif _mod_revision.is_null(revision_id):
3477
 
            # nothing to do:
3478
 
            return (0, [])
3479
 
        else:
3480
 
            revision_ids = self.search_missing_revision_ids(revision_id,
3481
 
                find_ghosts=find_ghosts).get_keys()
3482
 
            if len(revision_ids) == 0:
3483
 
                return (0, [])
3484
 
        return self._pack(self.source, self.target, revision_ids)
3485
 
 
3486
 
    def _pack(self, source, target, revision_ids):
3487
 
        from bzrlib.repofmt.pack_repo import Packer
3488
 
        packs = source._pack_collection.all_packs()
3489
 
        pack = Packer(self.target._pack_collection, packs, '.fetch',
3490
 
            revision_ids).pack()
3491
 
        if pack is not None:
3492
 
            self.target._pack_collection._save_pack_names()
3493
 
            copied_revs = pack.get_revision_count()
3494
 
            # Trigger an autopack. This may duplicate effort as we've just done
3495
 
            # a pack creation, but for now it is simpler to think about as
3496
 
            # 'upload data, then repack if needed'.
3497
 
            self.target._pack_collection.autopack()
3498
 
            return (copied_revs, [])
3499
 
        else:
3500
 
            return (0, [])
3501
 
 
3502
 
    @needs_read_lock
3503
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3504
 
        """See InterRepository.missing_revision_ids().
3505
 
 
3506
 
        :param find_ghosts: Find ghosts throughout the ancestry of
3507
 
            revision_id.
3508
 
        """
3509
 
        if not find_ghosts and revision_id is not None:
3510
 
            return self._walk_to_common_revisions([revision_id])
3511
 
        elif revision_id is not None:
3512
 
            # Find ghosts: search for revisions pointing from one repository to
3513
 
            # the other, and vice versa, anywhere in the history of revision_id.
3514
 
            graph = self.target.get_graph(other_repository=self.source)
3515
 
            searcher = graph._make_breadth_first_searcher([revision_id])
3516
 
            found_ids = set()
3517
 
            while True:
3518
 
                try:
3519
 
                    next_revs, ghosts = searcher.next_with_ghosts()
3520
 
                except StopIteration:
3521
 
                    break
3522
 
                if revision_id in ghosts:
3523
 
                    raise errors.NoSuchRevision(self.source, revision_id)
3524
 
                found_ids.update(next_revs)
3525
 
                found_ids.update(ghosts)
3526
 
            found_ids = frozenset(found_ids)
3527
 
            # Double query here: should be able to avoid this by changing the
3528
 
            # graph api further.
3529
 
            result_set = found_ids - frozenset(
3530
 
                self.target.get_parent_map(found_ids))
3531
 
        else:
3532
 
            source_ids = self.source.all_revision_ids()
3533
 
            # source_ids is the worst possible case we may need to pull.
3534
 
            # now we want to filter source_ids against what we actually
3535
 
            # have in target, but don't try to check for existence where we know
3536
 
            # we do not have a revision as that would be pointless.
3537
 
            target_ids = set(self.target.all_revision_ids())
3538
 
            result_set = set(source_ids).difference(target_ids)
3539
 
        return self.source.revision_ids_to_search_result(result_set)
3540
 
 
3541
 
 
3542
3483
class InterDifferingSerializer(InterRepository):
3543
3484
 
3544
3485
    @classmethod
3738
3679
        cache = lru_cache.LRUCache(100)
3739
3680
        cache[basis_id] = basis_tree
3740
3681
        del basis_tree # We don't want to hang on to it here
 
3682
        hints = []
3741
3683
        for offset in range(0, len(revision_ids), batch_size):
3742
3684
            self.target.start_write_group()
3743
3685
            try:
3749
3691
                self.target.abort_write_group()
3750
3692
                raise
3751
3693
            else:
3752
 
                self.target.commit_write_group()
 
3694
                hint = self.target.commit_write_group()
 
3695
                if hint:
 
3696
                    hints.extend(hint)
 
3697
        if hints and self.target._format.pack_compresses:
 
3698
            self.target.pack(hint=hints)
3753
3699
        pb.update('Transferring revisions', len(revision_ids),
3754
3700
                  len(revision_ids))
3755
3701
 
3819
3765
InterRepository.register_optimiser(InterSameDataRepository)
3820
3766
InterRepository.register_optimiser(InterWeaveRepo)
3821
3767
InterRepository.register_optimiser(InterKnitRepo)
3822
 
InterRepository.register_optimiser(InterPackRepo)
3823
3768
 
3824
3769
 
3825
3770
class CopyConverter(object):
4003
3948
        try:
4004
3949
            if resume_tokens:
4005
3950
                self.target_repo.resume_write_group(resume_tokens)
 
3951
                is_resume = True
4006
3952
            else:
4007
3953
                self.target_repo.start_write_group()
 
3954
                is_resume = False
4008
3955
            try:
4009
3956
                # locked_insert_stream performs a commit|suspend.
4010
 
                return self._locked_insert_stream(stream, src_format)
 
3957
                return self._locked_insert_stream(stream, src_format, is_resume)
4011
3958
            except:
4012
3959
                self.target_repo.abort_write_group(suppress_errors=True)
4013
3960
                raise
4014
3961
        finally:
4015
3962
            self.target_repo.unlock()
4016
3963
 
4017
 
    def _locked_insert_stream(self, stream, src_format):
 
3964
    def _locked_insert_stream(self, stream, src_format, is_resume):
4018
3965
        to_serializer = self.target_repo._format._serializer
4019
3966
        src_serializer = src_format._serializer
4020
3967
        new_pack = None
4070
4017
        if new_pack is not None:
4071
4018
            new_pack._write_data('', flush=True)
4072
4019
        # Find all the new revisions (including ones from resume_tokens)
4073
 
        missing_keys = self.target_repo.get_missing_parent_inventories()
 
4020
        missing_keys = self.target_repo.get_missing_parent_inventories(
 
4021
            check_for_missing_texts=is_resume)
4074
4022
        try:
4075
4023
            for prefix, versioned_file in (
4076
4024
                ('texts', self.target_repo.texts),
4077
4025
                ('inventories', self.target_repo.inventories),
4078
4026
                ('revisions', self.target_repo.revisions),
4079
4027
                ('signatures', self.target_repo.signatures),
 
4028
                ('chk_bytes', self.target_repo.chk_bytes),
4080
4029
                ):
 
4030
                if versioned_file is None:
 
4031
                    continue
4081
4032
                missing_keys.update((prefix,) + key for key in
4082
4033
                    versioned_file.get_missing_compression_parent_keys())
4083
4034
        except NotImplementedError:
4092
4043
                # missing keys can handle suspending a write group).
4093
4044
                write_group_tokens = self.target_repo.suspend_write_group()
4094
4045
                return write_group_tokens, missing_keys
4095
 
        self.target_repo.commit_write_group()
 
4046
        hint = self.target_repo.commit_write_group()
 
4047
        if (to_serializer != src_serializer and
 
4048
            self.target_repo._format.pack_compresses):
 
4049
            self.target_repo.pack(hint=hint)
4096
4050
        return [], set()
4097
4051
 
4098
4052
    def _extract_and_insert_inventories(self, substream, serializer):
4230
4184
        keys['texts'] = set()
4231
4185
        keys['revisions'] = set()
4232
4186
        keys['inventories'] = set()
 
4187
        keys['chk_bytes'] = set()
4233
4188
        keys['signatures'] = set()
4234
4189
        for key in missing_keys:
4235
4190
            keys[key[0]].add(key[1:])
4242
4197
                    keys['revisions'],))
4243
4198
        for substream_kind, keys in keys.iteritems():
4244
4199
            vf = getattr(self.from_repository, substream_kind)
 
4200
            if vf is None and keys:
 
4201
                    raise AssertionError(
 
4202
                        "cannot fill in keys for a versioned file we don't"
 
4203
                        " have: %s needs %s" % (substream_kind, keys))
 
4204
            if not keys:
 
4205
                # No need to stream something we don't have
 
4206
                continue
4245
4207
            # Ask for full texts always so that we don't need more round trips
4246
4208
            # after this stream.
4247
 
            stream = vf.get_record_stream(keys,
4248
 
                self.to_format._fetch_order, True)
 
4209
            # Some of the missing keys are genuinely ghosts, so filter absent
 
4210
            # records. The Sink is responsible for doing another check to
 
4211
            # ensure that ghosts don't introduce missing data for future
 
4212
            # fetches.
 
4213
            stream = versionedfile.filter_absent(vf.get_record_stream(keys,
 
4214
                self.to_format._fetch_order, True))
4249
4215
            yield substream_kind, stream
4250
4216
 
4251
4217
    def inventory_fetch_order(self):
4382
4348
            yield versionedfile.FulltextContentFactory(
4383
4349
                key, parent_keys, None, as_bytes)
4384
4350
 
 
4351
 
 
4352
def _iter_for_revno(repo, partial_history_cache, stop_index=None,
 
4353
                    stop_revision=None):
 
4354
    """Extend the partial history to include a given index
 
4355
 
 
4356
    If a stop_index is supplied, stop when that index has been reached.
 
4357
    If a stop_revision is supplied, stop when that revision is
 
4358
    encountered.  Otherwise, stop when the beginning of history is
 
4359
    reached.
 
4360
 
 
4361
    :param stop_index: The index which should be present.  When it is
 
4362
        present, history extension will stop.
 
4363
    :param stop_revision: The revision id which should be present.  When
 
4364
        it is encountered, history extension will stop.
 
4365
    """
 
4366
    start_revision = partial_history_cache[-1]
 
4367
    iterator = repo.iter_reverse_revision_history(start_revision)
 
4368
    try:
 
4369
        #skip the last revision in the list
 
4370
        iterator.next()
 
4371
        while True:
 
4372
            if (stop_index is not None and
 
4373
                len(partial_history_cache) > stop_index):
 
4374
                break
 
4375
            if partial_history_cache[-1] == stop_revision:
 
4376
                break
 
4377
            revision_id = iterator.next()
 
4378
            partial_history_cache.append(revision_id)
 
4379
    except StopIteration:
 
4380
        # No more history
 
4381
        return
 
4382