~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Vincent Ladeuil
  • Date: 2009-05-05 15:31:34 UTC
  • mto: (4343.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 4344.
  • Revision ID: v.ladeuil+lp@free.fr-20090505153134-q4bp4is9gywsmzrv
Clean up test for log formats.

* bzrlib/tests/blackbox/test_logformats.py:
Update tests to actual style.

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
        )
60
63
from bzrlib.trace import (
61
64
    log_exception_quietly, note, mutter, mutter_callsite, warning)
62
65
 
491
494
            ie.executable = content_summary[2]
492
495
            file_obj, stat_value = tree.get_file_with_stat(ie.file_id, path)
493
496
            try:
494
 
                text = file_obj.read()
 
497
                lines = file_obj.readlines()
495
498
            finally:
496
499
                file_obj.close()
497
500
            try:
498
501
                ie.text_sha1, ie.text_size = self._add_text_to_weave(
499
 
                    ie.file_id, text, heads, nostore_sha)
 
502
                    ie.file_id, lines, heads, nostore_sha)
500
503
                # Let the caller know we generated a stat fingerprint.
501
504
                fingerprint = (ie.text_sha1, stat_value)
502
505
            except errors.ExistingContent:
514
517
                # carry over:
515
518
                ie.revision = parent_entry.revision
516
519
                return self._get_delta(ie, basis_inv, path), False, None
517
 
            self._add_text_to_weave(ie.file_id, '', heads, None)
 
520
            lines = []
 
521
            self._add_text_to_weave(ie.file_id, lines, heads, None)
518
522
        elif kind == 'symlink':
519
523
            current_link_target = content_summary[3]
520
524
            if not store:
528
532
                ie.symlink_target = parent_entry.symlink_target
529
533
                return self._get_delta(ie, basis_inv, path), False, None
530
534
            ie.symlink_target = current_link_target
531
 
            self._add_text_to_weave(ie.file_id, '', heads, None)
 
535
            lines = []
 
536
            self._add_text_to_weave(ie.file_id, lines, heads, None)
532
537
        elif kind == 'tree-reference':
533
538
            if not store:
534
539
                if content_summary[3] != parent_entry.reference_revision:
539
544
                ie.revision = parent_entry.revision
540
545
                return self._get_delta(ie, basis_inv, path), False, None
541
546
            ie.reference_revision = content_summary[3]
542
 
            self._add_text_to_weave(ie.file_id, '', heads, None)
 
547
            lines = []
 
548
            self._add_text_to_weave(ie.file_id, lines, heads, None)
543
549
        else:
544
550
            raise NotImplementedError('unknown kind')
545
551
        ie.revision = self._new_revision_id
739
745
                        entry.executable = True
740
746
                    else:
741
747
                        entry.executable = False
742
 
                    if (carry_over_possible and
 
748
                    if (carry_over_possible and 
743
749
                        parent_entry.executable == entry.executable):
744
750
                            # Check the file length, content hash after reading
745
751
                            # the file.
748
754
                        nostore_sha = None
749
755
                    file_obj, stat_value = tree.get_file_with_stat(file_id, change[1][1])
750
756
                    try:
751
 
                        text = file_obj.read()
 
757
                        lines = file_obj.readlines()
752
758
                    finally:
753
759
                        file_obj.close()
754
760
                    try:
755
761
                        entry.text_sha1, entry.text_size = self._add_text_to_weave(
756
 
                            file_id, text, heads, nostore_sha)
 
762
                            file_id, lines, heads, nostore_sha)
757
763
                        yield file_id, change[1][1], (entry.text_sha1, stat_value)
758
764
                    except errors.ExistingContent:
759
765
                        # No content change against a carry_over parent
768
774
                        parent_entry.symlink_target == entry.symlink_target):
769
775
                        carried_over = True
770
776
                    else:
771
 
                        self._add_text_to_weave(change[0], '', heads, None)
 
777
                        self._add_text_to_weave(change[0], [], heads, None)
772
778
                elif kind == 'directory':
773
779
                    if carry_over_possible:
774
780
                        carried_over = True
776
782
                        # Nothing to set on the entry.
777
783
                        # XXX: split into the Root and nonRoot versions.
778
784
                        if change[1][1] != '' or self.repository.supports_rich_root():
779
 
                            self._add_text_to_weave(change[0], '', heads, None)
 
785
                            self._add_text_to_weave(change[0], [], heads, None)
780
786
                elif kind == 'tree-reference':
781
787
                    if not self.repository._format.supports_tree_reference:
782
788
                        # This isn't quite sane as an error, but we shouldn't
785
791
                        # references.
786
792
                        raise errors.UnsupportedOperation(tree.add_reference,
787
793
                            self.repository)
788
 
                    reference_revision = tree.get_reference_revision(change[0])
789
 
                    entry.reference_revision = reference_revision
 
794
                    entry.reference_revision = \
 
795
                        tree.get_reference_revision(change[0])
790
796
                    if (carry_over_possible and
791
797
                        parent_entry.reference_revision == reference_revision):
792
798
                        carried_over = True
793
799
                    else:
794
 
                        self._add_text_to_weave(change[0], '', heads, None)
 
800
                        self._add_text_to_weave(change[0], [], heads, None)
795
801
                else:
796
802
                    raise AssertionError('unknown kind %r' % kind)
797
803
                if not carried_over:
812
818
            self._require_root_change(tree)
813
819
        self.basis_delta_revision = basis_revision_id
814
820
 
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]
 
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]
820
832
 
821
833
 
822
834
class RootCommitBuilder(CommitBuilder):
848
860
######################################################################
849
861
# Repositories
850
862
 
851
 
 
852
863
class Repository(object):
853
864
    """Repository holding history for one or more branches.
854
865
 
958
969
        """
959
970
        if not self._format.supports_external_lookups:
960
971
            raise errors.UnstackableRepositoryFormat(self._format, self.base)
961
 
        if self.is_locked():
962
 
            # This repository will call fallback.unlock() when we transition to
963
 
            # the unlocked state, so we make sure to increment the lock count
964
 
            repository.lock_read()
965
972
        self._check_fallback_repository(repository)
966
973
        self._fallback_repositories.append(repository)
967
974
        self.texts.add_fallback_versioned_files(repository.texts)
1016
1023
                               parents, basis_inv=None, propagate_caches=False):
1017
1024
        """Add a new inventory expressed as a delta against another revision.
1018
1025
 
1019
 
        See the inventory developers documentation for the theory behind
1020
 
        inventory deltas.
1021
 
 
1022
1026
        :param basis_revision_id: The inventory id the delta was created
1023
1027
            against. (This does not have to be a direct parent.)
1024
1028
        :param delta: The inventory delta (see Inventory.apply_delta for
1146
1150
        # The old API returned a list, should this actually be a set?
1147
1151
        return parent_map.keys()
1148
1152
 
1149
 
    def _check_inventories(self, checker):
1150
 
        """Check the inventories found from the revision scan.
1151
 
        
1152
 
        This is responsible for verifying the sha1 of inventories and
1153
 
        creating a pending_keys set that covers data referenced by inventories.
1154
 
        """
1155
 
        bar = ui.ui_factory.nested_progress_bar()
1156
 
        try:
1157
 
            self._do_check_inventories(checker, bar)
1158
 
        finally:
1159
 
            bar.finished()
1160
 
 
1161
 
    def _do_check_inventories(self, checker, bar):
1162
 
        """Helper for _check_inventories."""
1163
 
        revno = 0
1164
 
        keys = {'chk_bytes':set(), 'inventories':set(), 'texts':set()}
1165
 
        kinds = ['chk_bytes', 'texts']
1166
 
        count = len(checker.pending_keys)
1167
 
        bar.update("inventories", 0, 2)
1168
 
        current_keys = checker.pending_keys
1169
 
        checker.pending_keys = {}
1170
 
        # Accumulate current checks.
1171
 
        for key in current_keys:
1172
 
            if key[0] != 'inventories' and key[0] not in kinds:
1173
 
                checker._report_items.append('unknown key type %r' % (key,))
1174
 
            keys[key[0]].add(key[1:])
1175
 
        if keys['inventories']:
1176
 
            # NB: output order *should* be roughly sorted - topo or
1177
 
            # inverse topo depending on repository - either way decent
1178
 
            # to just delta against. However, pre-CHK formats didn't
1179
 
            # try to optimise inventory layout on disk. As such the
1180
 
            # pre-CHK code path does not use inventory deltas.
1181
 
            last_object = None
1182
 
            for record in self.inventories.check(keys=keys['inventories']):
1183
 
                if record.storage_kind == 'absent':
1184
 
                    checker._report_items.append(
1185
 
                        'Missing inventory {%s}' % (record.key,))
1186
 
                else:
1187
 
                    last_object = self._check_record('inventories', record,
1188
 
                        checker, last_object,
1189
 
                        current_keys[('inventories',) + record.key])
1190
 
            del keys['inventories']
1191
 
        else:
1192
 
            return
1193
 
        bar.update("texts", 1)
1194
 
        while (checker.pending_keys or keys['chk_bytes']
1195
 
            or keys['texts']):
1196
 
            # Something to check.
1197
 
            current_keys = checker.pending_keys
1198
 
            checker.pending_keys = {}
1199
 
            # Accumulate current checks.
1200
 
            for key in current_keys:
1201
 
                if key[0] not in kinds:
1202
 
                    checker._report_items.append('unknown key type %r' % (key,))
1203
 
                keys[key[0]].add(key[1:])
1204
 
            # Check the outermost kind only - inventories || chk_bytes || texts
1205
 
            for kind in kinds:
1206
 
                if keys[kind]:
1207
 
                    last_object = None
1208
 
                    for record in getattr(self, kind).check(keys=keys[kind]):
1209
 
                        if record.storage_kind == 'absent':
1210
 
                            checker._report_items.append(
1211
 
                                'Missing inventory {%s}' % (record.key,))
1212
 
                        else:
1213
 
                            last_object = self._check_record(kind, record,
1214
 
                                checker, last_object, current_keys[(kind,) + record.key])
1215
 
                    keys[kind] = set()
1216
 
                    break
1217
 
 
1218
 
    def _check_record(self, kind, record, checker, last_object, item_data):
1219
 
        """Check a single text from this repository."""
1220
 
        if kind == 'inventories':
1221
 
            rev_id = record.key[0]
1222
 
            inv = self.deserialise_inventory(rev_id,
1223
 
                record.get_bytes_as('fulltext'))
1224
 
            if last_object is not None:
1225
 
                delta = inv._make_delta(last_object)
1226
 
                for old_path, path, file_id, ie in delta:
1227
 
                    if ie is None:
1228
 
                        continue
1229
 
                    ie.check(checker, rev_id, inv)
1230
 
            else:
1231
 
                for path, ie in inv.iter_entries():
1232
 
                    ie.check(checker, rev_id, inv)
1233
 
            if self._format.fast_deltas:
1234
 
                return inv
1235
 
        elif kind == 'chk_bytes':
1236
 
            # No code written to check chk_bytes for this repo format.
1237
 
            checker._report_items.append(
1238
 
                'unsupported key type chk_bytes for %s' % (record.key,))
1239
 
        elif kind == 'texts':
1240
 
            self._check_text(record, checker, item_data)
1241
 
        else:
1242
 
            checker._report_items.append(
1243
 
                'unknown key type %s for %s' % (kind, record.key))
1244
 
 
1245
 
    def _check_text(self, record, checker, item_data):
1246
 
        """Check a single text."""
1247
 
        # Check it is extractable.
1248
 
        # TODO: check length.
1249
 
        if record.storage_kind == 'chunked':
1250
 
            chunks = record.get_bytes_as(record.storage_kind)
1251
 
            sha1 = osutils.sha_strings(chunks)
1252
 
            length = sum(map(len, chunks))
1253
 
        else:
1254
 
            content = record.get_bytes_as('fulltext')
1255
 
            sha1 = osutils.sha_string(content)
1256
 
            length = len(content)
1257
 
        if item_data and sha1 != item_data[1]:
1258
 
            checker._report_items.append(
1259
 
                'sha1 mismatch: %s has sha1 %s expected %s referenced by %s' %
1260
 
                (record.key, sha1, item_data[1], item_data[2]))
1261
 
 
1262
1153
    @staticmethod
1263
1154
    def create(a_bzrdir):
1264
1155
        """Construct the current default format repository in a_bzrdir."""
1297
1188
        self._inventory_entry_cache = fifo_cache.FIFOCache(10*1024)
1298
1189
 
1299
1190
    def __repr__(self):
1300
 
        if self._fallback_repositories:
1301
 
            return '%s(%r, fallback_repositories=%r)' % (
1302
 
                self.__class__.__name__,
1303
 
                self.base,
1304
 
                self._fallback_repositories)
1305
 
        else:
1306
 
            return '%s(%r)' % (self.__class__.__name__,
1307
 
                               self.base)
1308
 
 
1309
 
    def _has_same_fallbacks(self, other_repo):
1310
 
        """Returns true if the repositories have the same fallbacks."""
1311
 
        my_fb = self._fallback_repositories
1312
 
        other_fb = other_repo._fallback_repositories
1313
 
        if len(my_fb) != len(other_fb):
1314
 
            return False
1315
 
        for f, g in zip(my_fb, other_fb):
1316
 
            if not f.has_same_location(g):
1317
 
                return False
1318
 
        return True
 
1191
        return '%s(%r)' % (self.__class__.__name__,
 
1192
                           self.base)
1319
1193
 
1320
1194
    def has_same_location(self, other):
1321
1195
        """Returns a boolean indicating if this repository is at the same
1366
1240
        """
1367
1241
        locked = self.is_locked()
1368
1242
        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()
1369
1246
        if not locked:
1370
 
            for repo in self._fallback_repositories:
1371
 
                # Writes don't affect fallback repos
1372
 
                repo.lock_read()
1373
1247
            self._refresh_data()
1374
1248
        return result
1375
1249
 
1376
1250
    def lock_read(self):
1377
1251
        locked = self.is_locked()
1378
1252
        self.control_files.lock_read()
 
1253
        for repo in self._fallback_repositories:
 
1254
            repo.lock_read()
1379
1255
        if not locked:
1380
 
            for repo in self._fallback_repositories:
1381
 
                repo.lock_read()
1382
1256
            self._refresh_data()
1383
1257
 
1384
1258
    def get_physical_lock_status(self):
1535
1409
            raise errors.BzrError('mismatched lock context %r and '
1536
1410
                'write group %r.' %
1537
1411
                (self.get_transaction(), self._write_group))
1538
 
        result = self._commit_write_group()
 
1412
        self._commit_write_group()
1539
1413
        self._write_group = None
1540
 
        return result
1541
1414
 
1542
1415
    def _commit_write_group(self):
1543
1416
        """Template method for per-repository write group cleanup.
1551
1424
    def suspend_write_group(self):
1552
1425
        raise errors.UnsuspendableWriteGroup(self)
1553
1426
 
1554
 
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
 
1427
    def get_missing_parent_inventories(self):
1555
1428
        """Return the keys of missing inventory parents for revisions added in
1556
1429
        this write group.
1557
1430
 
1566
1439
            return set()
1567
1440
        if not self.is_in_write_group():
1568
1441
            raise AssertionError('not in a write group')
1569
 
 
 
1442
                
1570
1443
        # XXX: We assume that every added revision already has its
1571
1444
        # corresponding inventory, so we only check for parent inventories that
1572
1445
        # might be missing, rather than all inventories.
1576
1449
        present_inventories = unstacked_inventories.get_parent_map(
1577
1450
            key[-1:] for key in parents)
1578
1451
        parents.difference_update(present_inventories)
1579
 
        if len(parents) == 0:
1580
 
            # No missing parent inventories.
1581
 
            return set()
1582
 
        if not check_for_missing_texts:
1583
 
            return set(('inventories', rev_id) for (rev_id,) in parents)
1584
 
        # Ok, now we have a list of missing inventories.  But these only matter
1585
 
        # if the inventories that reference them are missing some texts they
1586
 
        # appear to introduce.
1587
 
        # XXX: Texts referenced by all added inventories need to be present,
1588
 
        # but at the moment we're only checking for texts referenced by
1589
 
        # inventories at the graph's edge.
1590
 
        key_deps = self.revisions._index._key_dependencies
1591
 
        key_deps.add_keys(present_inventories)
1592
 
        referrers = frozenset(r[0] for r in key_deps.get_referrers())
1593
 
        file_ids = self.fileids_altered_by_revision_ids(referrers)
1594
 
        missing_texts = set()
1595
 
        for file_id, version_ids in file_ids.iteritems():
1596
 
            missing_texts.update(
1597
 
                (file_id, version_id) for version_id in version_ids)
1598
 
        present_texts = self.texts.get_parent_map(missing_texts)
1599
 
        missing_texts.difference_update(present_texts)
1600
 
        if not missing_texts:
1601
 
            # No texts are missing, so all revisions and their deltas are
1602
 
            # reconstructable.
1603
 
            return set()
1604
 
        # Alternatively the text versions could be returned as the missing
1605
 
        # keys, but this is likely to be less data.
1606
1452
        missing_keys = set(('inventories', rev_id) for (rev_id,) in parents)
1607
1453
        return missing_keys
1608
1454
 
1660
1506
            raise errors.InternalBzrError(
1661
1507
                "May not fetch while in a write group.")
1662
1508
        # fast path same-url fetch operations
1663
 
        # TODO: lift out to somewhere common with RemoteRepository
1664
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
1665
 
        if (self.has_same_location(source)
1666
 
            and fetch_spec is None
1667
 
            and self._has_same_fallbacks(source)):
 
1509
        if self.has_same_location(source) and fetch_spec is None:
1668
1510
            # check that last_revision is in 'from' and then return a
1669
1511
            # no-operation.
1670
1512
            if (revision_id is not None and
1711
1553
        self.control_files.unlock()
1712
1554
        if self.control_files._lock_count == 0:
1713
1555
            self._inventory_entry_cache.clear()
1714
 
            for repo in self._fallback_repositories:
1715
 
                repo.unlock()
 
1556
        for repo in self._fallback_repositories:
 
1557
            repo.unlock()
1716
1558
 
1717
1559
    @needs_read_lock
1718
1560
    def clone(self, a_bzrdir, revision_id=None):
1827
1669
 
1828
1670
    @needs_read_lock
1829
1671
    def get_revisions(self, revision_ids):
1830
 
        """Get many revisions at once.
1831
 
        
1832
 
        Repositories that need to check data on every revision read should 
1833
 
        subclass this method.
1834
 
        """
 
1672
        """Get many revisions at once."""
1835
1673
        return self._get_revisions(revision_ids)
1836
1674
 
1837
1675
    @needs_read_lock
1838
1676
    def _get_revisions(self, revision_ids):
1839
1677
        """Core work logic to get many revisions without sanity checks."""
 
1678
        for rev_id in revision_ids:
 
1679
            if not rev_id or not isinstance(rev_id, basestring):
 
1680
                raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
 
1681
        keys = [(key,) for key in revision_ids]
 
1682
        stream = self.revisions.get_record_stream(keys, 'unordered', True)
1840
1683
        revs = {}
1841
 
        for revid, rev in self._iter_revisions(revision_ids):
1842
 
            if rev is None:
1843
 
                raise errors.NoSuchRevision(self, revid)
1844
 
            revs[revid] = rev
 
1684
        for record in stream:
 
1685
            if record.storage_kind == 'absent':
 
1686
                raise errors.NoSuchRevision(self, record.key[0])
 
1687
            text = record.get_bytes_as('fulltext')
 
1688
            rev = self._serializer.read_revision_from_string(text)
 
1689
            revs[record.key[0]] = rev
1845
1690
        return [revs[revid] for revid in revision_ids]
1846
1691
 
1847
 
    def _iter_revisions(self, revision_ids):
1848
 
        """Iterate over revision objects.
1849
 
 
1850
 
        :param revision_ids: An iterable of revisions to examine. None may be
1851
 
            passed to request all revisions known to the repository. Note that
1852
 
            not all repositories can find unreferenced revisions; for those
1853
 
            repositories only referenced ones will be returned.
1854
 
        :return: An iterator of (revid, revision) tuples. Absent revisions (
1855
 
            those asked for but not available) are returned as (revid, None).
1856
 
        """
1857
 
        if revision_ids is None:
1858
 
            revision_ids = self.all_revision_ids()
1859
 
        else:
1860
 
            for rev_id in revision_ids:
1861
 
                if not rev_id or not isinstance(rev_id, basestring):
1862
 
                    raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
1863
 
        keys = [(key,) for key in revision_ids]
1864
 
        stream = self.revisions.get_record_stream(keys, 'unordered', True)
1865
 
        for record in stream:
1866
 
            revid = record.key[0]
1867
 
            if record.storage_kind == 'absent':
1868
 
                yield (revid, None)
1869
 
            else:
1870
 
                text = record.get_bytes_as('fulltext')
1871
 
                rev = self._serializer.read_revision_from_string(text)
1872
 
                yield (revid, rev)
1873
 
 
1874
1692
    @needs_read_lock
1875
1693
    def get_revision_xml(self, revision_id):
1876
1694
        # TODO: jam 20070210 This shouldn't be necessary since get_revision
2070
1888
                    yield line, revid
2071
1889
 
2072
1890
    def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
2073
 
        revision_keys):
 
1891
        revision_ids):
2074
1892
        """Helper routine for fileids_altered_by_revision_ids.
2075
1893
 
2076
1894
        This performs the translation of xml lines to revision ids.
2077
1895
 
2078
1896
        :param line_iterator: An iterator of lines, origin_version_id
2079
 
        :param revision_keys: The revision ids to filter for. This should be a
 
1897
        :param revision_ids: The revision ids to filter for. This should be a
2080
1898
            set or other type which supports efficient __contains__ lookups, as
2081
 
            the revision key from each parsed line will be looked up in the
2082
 
            revision_keys filter.
 
1899
            the revision id from each parsed line will be looked up in the
 
1900
            revision_ids filter.
2083
1901
        :return: a dictionary mapping altered file-ids to an iterable of
2084
1902
        revision_ids. Each altered file-ids has the exact revision_ids that
2085
1903
        altered it listed explicitly.
2086
1904
        """
2087
1905
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
2088
1906
                line_iterator).iterkeys())
2089
 
        parent_keys = self._find_parent_keys_of_revisions(revision_keys)
 
1907
        # Note that revision_ids are revision keys.
 
1908
        parent_maps = self.revisions.get_parent_map(revision_ids)
 
1909
        parents = set()
 
1910
        map(parents.update, parent_maps.itervalues())
 
1911
        parents.difference_update(revision_ids)
2090
1912
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
2091
 
            self._inventory_xml_lines_for_keys(parent_keys)))
 
1913
            self._inventory_xml_lines_for_keys(parents)))
2092
1914
        new_keys = seen - parent_seen
2093
1915
        result = {}
2094
1916
        setdefault = result.setdefault
2096
1918
            setdefault(key[0], set()).add(key[-1])
2097
1919
        return result
2098
1920
 
2099
 
    def _find_parent_ids_of_revisions(self, revision_ids):
2100
 
        """Find all parent ids that are mentioned in the revision graph.
2101
 
 
2102
 
        :return: set of revisions that are parents of revision_ids which are
2103
 
            not part of revision_ids themselves
2104
 
        """
2105
 
        parent_map = self.get_parent_map(revision_ids)
2106
 
        parent_ids = set()
2107
 
        map(parent_ids.update, parent_map.itervalues())
2108
 
        parent_ids.difference_update(revision_ids)
2109
 
        parent_ids.discard(_mod_revision.NULL_REVISION)
2110
 
        return parent_ids
2111
 
 
2112
 
    def _find_parent_keys_of_revisions(self, revision_keys):
2113
 
        """Similar to _find_parent_ids_of_revisions, but used with keys.
2114
 
 
2115
 
        :param revision_keys: An iterable of revision_keys.
2116
 
        :return: The parents of all revision_keys that are not already in
2117
 
            revision_keys
2118
 
        """
2119
 
        parent_map = self.revisions.get_parent_map(revision_keys)
2120
 
        parent_keys = set()
2121
 
        map(parent_keys.update, parent_map.itervalues())
2122
 
        parent_keys.difference_update(revision_keys)
2123
 
        parent_keys.discard(_mod_revision.NULL_REVISION)
2124
 
        return parent_keys
2125
 
 
2126
1921
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
2127
1922
        """Find the file ids and versions affected by revisions.
2128
1923
 
2230
2025
                batch_size]
2231
2026
            if not to_query:
2232
2027
                break
2233
 
            for revision_id in to_query:
 
2028
            for rev_tree in self.revision_trees(to_query):
 
2029
                revision_id = rev_tree.get_revision_id()
2234
2030
                parent_ids = ancestors[revision_id]
2235
2031
                for text_key in revision_keys[revision_id]:
2236
2032
                    pb.update("Calculating text parents", processed_texts)
2407
2203
        """
2408
2204
        return self.get_revision(revision_id).inventory_sha1
2409
2205
 
2410
 
    def get_rev_id_for_revno(self, revno, known_pair):
2411
 
        """Return the revision id of a revno, given a later (revno, revid)
2412
 
        pair in the same history.
2413
 
 
2414
 
        :return: if found (True, revid).  If the available history ran out
2415
 
            before reaching the revno, then this returns
2416
 
            (False, (closest_revno, closest_revid)).
2417
 
        """
2418
 
        known_revno, known_revid = known_pair
2419
 
        partial_history = [known_revid]
2420
 
        distance_from_known = known_revno - revno
2421
 
        if distance_from_known < 0:
2422
 
            raise ValueError(
2423
 
                'requested revno (%d) is later than given known revno (%d)'
2424
 
                % (revno, known_revno))
2425
 
        try:
2426
 
            _iter_for_revno(
2427
 
                self, partial_history, stop_index=distance_from_known)
2428
 
        except errors.RevisionNotPresent, err:
2429
 
            if err.revision_id == known_revid:
2430
 
                # The start revision (known_revid) wasn't found.
2431
 
                raise
2432
 
            # This is a stacked repository with no fallbacks, or a there's a
2433
 
            # left-hand ghost.  Either way, even though the revision named in
2434
 
            # the error isn't in this repo, we know it's the next step in this
2435
 
            # left-hand history.
2436
 
            partial_history.append(err.revision_id)
2437
 
        if len(partial_history) <= distance_from_known:
2438
 
            # Didn't find enough history to get a revid for the revno.
2439
 
            earliest_revno = known_revno - len(partial_history) + 1
2440
 
            return (False, (earliest_revno, partial_history[-1]))
2441
 
        if len(partial_history) - 1 > distance_from_known:
2442
 
            raise AssertionError('_iter_for_revno returned too much history')
2443
 
        return (True, partial_history[-1])
2444
 
 
2445
2206
    def iter_reverse_revision_history(self, revision_id):
2446
2207
        """Iterate backwards through revision ids in the lefthand history
2447
2208
 
2453
2214
        while True:
2454
2215
            if next_id in (None, _mod_revision.NULL_REVISION):
2455
2216
                return
2456
 
            try:
2457
 
                parents = graph.get_parent_map([next_id])[next_id]
2458
 
            except KeyError:
2459
 
                raise errors.RevisionNotPresent(next_id, self)
2460
2217
            yield next_id
 
2218
            # Note: The following line may raise KeyError in the event of
 
2219
            # truncated history. We decided not to have a try:except:raise
 
2220
            # RevisionNotPresent here until we see a use for it, because of the
 
2221
            # cost in an inner loop that is by its very nature O(history).
 
2222
            # Robert Collins 20080326
 
2223
            parents = graph.get_parent_map([next_id])[next_id]
2461
2224
            if len(parents) == 0:
2462
2225
                return
2463
2226
            else:
2577
2340
            keys = tsort.topo_sort(parent_map)
2578
2341
        return [None] + list(keys)
2579
2342
 
2580
 
    def pack(self, hint=None):
 
2343
    def pack(self):
2581
2344
        """Compress the data within the repository.
2582
2345
 
2583
2346
        This operation only makes sense for some repository types. For other
2586
2349
        This stub method does not require a lock, but subclasses should use
2587
2350
        @needs_write_lock as this is a long running call its reasonable to
2588
2351
        implicitly lock for the user.
2589
 
 
2590
 
        :param hint: If not supplied, the whole repository is packed.
2591
 
            If supplied, the repository may use the hint parameter as a
2592
 
            hint for the parts of the repository to pack. A hint can be
2593
 
            obtained from the result of commit_write_group(). Out of
2594
 
            date hints are simply ignored, because concurrent operations
2595
 
            can obsolete them rapidly.
2596
2352
        """
2597
2353
 
2598
2354
    def get_transaction(self):
2599
2355
        return self.control_files.get_transaction()
2600
2356
 
2601
2357
    def get_parent_map(self, revision_ids):
2602
 
        """See graph.StackedParentsProvider.get_parent_map"""
 
2358
        """See graph._StackedParentsProvider.get_parent_map"""
2603
2359
        # revisions index works in keys; this just works in revisions
2604
2360
        # therefore wrap and unwrap
2605
2361
        query_keys = []
2628
2384
        parents_provider = self._make_parents_provider()
2629
2385
        if (other_repository is not None and
2630
2386
            not self.has_same_location(other_repository)):
2631
 
            parents_provider = graph.StackedParentsProvider(
 
2387
            parents_provider = graph._StackedParentsProvider(
2632
2388
                [parents_provider, other_repository._make_parents_provider()])
2633
2389
        return graph.Graph(parents_provider)
2634
2390
 
2635
 
    def _get_versioned_file_checker(self, text_key_references=None,
2636
 
        ancestors=None):
 
2391
    def _get_versioned_file_checker(self, text_key_references=None):
2637
2392
        """Return an object suitable for checking versioned files.
2638
2393
        
2639
2394
        :param text_key_references: if non-None, an already built
2641
2396
            to whether they were referred to by the inventory of the
2642
2397
            revision_id that they contain. If None, this will be
2643
2398
            calculated.
2644
 
        :param ancestors: Optional result from
2645
 
            self.get_graph().get_parent_map(self.all_revision_ids()) if already
2646
 
            available.
2647
2399
        """
2648
2400
        return _VersionedFileChecker(self,
2649
 
            text_key_references=text_key_references, ancestors=ancestors)
 
2401
            text_key_references=text_key_references)
2650
2402
 
2651
2403
    def revision_ids_to_search_result(self, result_set):
2652
2404
        """Convert a set of revision ids to a graph SearchResult."""
2702
2454
        return record.get_bytes_as('fulltext')
2703
2455
 
2704
2456
    @needs_read_lock
2705
 
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
 
2457
    def check(self, revision_ids=None):
2706
2458
        """Check consistency of all history of given revision_ids.
2707
2459
 
2708
2460
        Different repository implementations should override _check().
2709
2461
 
2710
2462
        :param revision_ids: A non-empty list of revision_ids whose ancestry
2711
2463
             will be checked.  Typically the last revision_id of a branch.
2712
 
        :param callback_refs: A dict of check-refs to resolve and callback
2713
 
            the check/_check method on the items listed as wanting the ref.
2714
 
            see bzrlib.check.
2715
 
        :param check_repo: If False do not check the repository contents, just 
2716
 
            calculate the data callback_refs requires and call them back.
2717
2464
        """
2718
 
        return self._check(revision_ids, callback_refs=callback_refs,
2719
 
            check_repo=check_repo)
 
2465
        return self._check(revision_ids)
2720
2466
 
2721
 
    def _check(self, revision_ids, callback_refs, check_repo):
2722
 
        result = check.Check(self, check_repo=check_repo)
2723
 
        result.check(callback_refs)
 
2467
    def _check(self, revision_ids):
 
2468
        result = check.Check(self)
 
2469
        result.check()
2724
2470
        return result
2725
2471
 
2726
2472
    def _warn_if_deprecated(self):
3011
2757
    # Does this format have < O(tree_size) delta generation. Used to hint what
3012
2758
    # code path for commit, amongst other things.
3013
2759
    fast_deltas = None
3014
 
    # Does doing a pack operation compress data? Useful for the pack UI command
3015
 
    # (so if there is one pack, the operation can still proceed because it may
3016
 
    # help), and for fetching when data won't have come from the same
3017
 
    # compressor.
3018
 
    pack_compresses = False
3019
2760
 
3020
2761
    def __str__(self):
3021
2762
        return "<%s>" % self.__class__.__name__
3290
3031
    'RepositoryFormatCHK1',
3291
3032
    )
3292
3033
 
3293
 
format_registry.register_lazy(
3294
 
    'Bazaar development format - chk repository with bencode revision '
3295
 
        'serialization (needs bzr.dev from 1.16)\n',
3296
 
    'bzrlib.repofmt.groupcompress_repo',
3297
 
    'RepositoryFormatCHK2',
3298
 
    )
3299
 
format_registry.register_lazy(
3300
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
3301
 
    'bzrlib.repofmt.groupcompress_repo',
3302
 
    'RepositoryFormat2a',
3303
 
    )
3304
 
 
3305
3034
 
3306
3035
class InterRepository(InterObject):
3307
3036
    """This class represents operations taking place between two repositories.
3648
3377
        return self.source.revision_ids_to_search_result(result_set)
3649
3378
 
3650
3379
 
 
3380
class InterPackRepo(InterSameDataRepository):
 
3381
    """Optimised code paths between Pack based repositories."""
 
3382
 
 
3383
    @classmethod
 
3384
    def _get_repo_format_to_test(self):
 
3385
        from bzrlib.repofmt import pack_repo
 
3386
        return pack_repo.RepositoryFormatKnitPack6RichRoot()
 
3387
 
 
3388
    @staticmethod
 
3389
    def is_compatible(source, target):
 
3390
        """Be compatible with known Pack formats.
 
3391
 
 
3392
        We don't test for the stores being of specific types because that
 
3393
        could lead to confusing results, and there is no need to be
 
3394
        overly general.
 
3395
 
 
3396
        InterPackRepo does not support CHK based repositories.
 
3397
        """
 
3398
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
 
3399
        from bzrlib.repofmt.groupcompress_repo import RepositoryFormatCHK1
 
3400
        try:
 
3401
            are_packs = (isinstance(source._format, RepositoryFormatPack) and
 
3402
                isinstance(target._format, RepositoryFormatPack))
 
3403
            not_packs = (isinstance(source._format, RepositoryFormatCHK1) or
 
3404
                isinstance(target._format, RepositoryFormatCHK1))
 
3405
        except AttributeError:
 
3406
            return False
 
3407
        if not_packs or not are_packs:
 
3408
            return False
 
3409
        return InterRepository._same_model(source, target)
 
3410
 
 
3411
    @needs_write_lock
 
3412
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3413
            fetch_spec=None):
 
3414
        """See InterRepository.fetch()."""
 
3415
        if (len(self.source._fallback_repositories) > 0 or
 
3416
            len(self.target._fallback_repositories) > 0):
 
3417
            # The pack layer is not aware of fallback repositories, so when
 
3418
            # fetching from a stacked repository or into a stacked repository
 
3419
            # we use the generic fetch logic which uses the VersionedFiles
 
3420
            # attributes on repository.
 
3421
            from bzrlib.fetch import RepoFetcher
 
3422
            fetcher = RepoFetcher(self.target, self.source, revision_id,
 
3423
                    pb, find_ghosts, fetch_spec=fetch_spec)
 
3424
        if fetch_spec is not None:
 
3425
            if len(list(fetch_spec.heads)) != 1:
 
3426
                raise AssertionError(
 
3427
                    "InterPackRepo.fetch doesn't support "
 
3428
                    "fetching multiple heads yet.")
 
3429
            revision_id = list(fetch_spec.heads)[0]
 
3430
            fetch_spec = None
 
3431
        if revision_id is None:
 
3432
            # TODO:
 
3433
            # everything to do - use pack logic
 
3434
            # to fetch from all packs to one without
 
3435
            # inventory parsing etc, IFF nothing to be copied is in the target.
 
3436
            # till then:
 
3437
            source_revision_ids = frozenset(self.source.all_revision_ids())
 
3438
            revision_ids = source_revision_ids - \
 
3439
                frozenset(self.target.get_parent_map(source_revision_ids))
 
3440
            revision_keys = [(revid,) for revid in revision_ids]
 
3441
            index = self.target._pack_collection.revision_index.combined_index
 
3442
            present_revision_ids = set(item[1][0] for item in
 
3443
                index.iter_entries(revision_keys))
 
3444
            revision_ids = set(revision_ids) - present_revision_ids
 
3445
            # implementing the TODO will involve:
 
3446
            # - detecting when all of a pack is selected
 
3447
            # - avoiding as much as possible pre-selection, so the
 
3448
            # more-core routines such as create_pack_from_packs can filter in
 
3449
            # a just-in-time fashion. (though having a HEADS list on a
 
3450
            # repository might make this a lot easier, because we could
 
3451
            # sensibly detect 'new revisions' without doing a full index scan.
 
3452
        elif _mod_revision.is_null(revision_id):
 
3453
            # nothing to do:
 
3454
            return (0, [])
 
3455
        else:
 
3456
            revision_ids = self.search_missing_revision_ids(revision_id,
 
3457
                find_ghosts=find_ghosts).get_keys()
 
3458
            if len(revision_ids) == 0:
 
3459
                return (0, [])
 
3460
        return self._pack(self.source, self.target, revision_ids)
 
3461
 
 
3462
    def _pack(self, source, target, revision_ids):
 
3463
        from bzrlib.repofmt.pack_repo import Packer
 
3464
        packs = source._pack_collection.all_packs()
 
3465
        pack = Packer(self.target._pack_collection, packs, '.fetch',
 
3466
            revision_ids).pack()
 
3467
        if pack is not None:
 
3468
            self.target._pack_collection._save_pack_names()
 
3469
            copied_revs = pack.get_revision_count()
 
3470
            # Trigger an autopack. This may duplicate effort as we've just done
 
3471
            # a pack creation, but for now it is simpler to think about as
 
3472
            # 'upload data, then repack if needed'.
 
3473
            self.target._pack_collection.autopack()
 
3474
            return (copied_revs, [])
 
3475
        else:
 
3476
            return (0, [])
 
3477
 
 
3478
    @needs_read_lock
 
3479
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
3480
        """See InterRepository.missing_revision_ids().
 
3481
 
 
3482
        :param find_ghosts: Find ghosts throughout the ancestry of
 
3483
            revision_id.
 
3484
        """
 
3485
        if not find_ghosts and revision_id is not None:
 
3486
            return self._walk_to_common_revisions([revision_id])
 
3487
        elif revision_id is not None:
 
3488
            # Find ghosts: search for revisions pointing from one repository to
 
3489
            # the other, and vice versa, anywhere in the history of revision_id.
 
3490
            graph = self.target.get_graph(other_repository=self.source)
 
3491
            searcher = graph._make_breadth_first_searcher([revision_id])
 
3492
            found_ids = set()
 
3493
            while True:
 
3494
                try:
 
3495
                    next_revs, ghosts = searcher.next_with_ghosts()
 
3496
                except StopIteration:
 
3497
                    break
 
3498
                if revision_id in ghosts:
 
3499
                    raise errors.NoSuchRevision(self.source, revision_id)
 
3500
                found_ids.update(next_revs)
 
3501
                found_ids.update(ghosts)
 
3502
            found_ids = frozenset(found_ids)
 
3503
            # Double query here: should be able to avoid this by changing the
 
3504
            # graph api further.
 
3505
            result_set = found_ids - frozenset(
 
3506
                self.target.get_parent_map(found_ids))
 
3507
        else:
 
3508
            source_ids = self.source.all_revision_ids()
 
3509
            # source_ids is the worst possible case we may need to pull.
 
3510
            # now we want to filter source_ids against what we actually
 
3511
            # have in target, but don't try to check for existence where we know
 
3512
            # we do not have a revision as that would be pointless.
 
3513
            target_ids = set(self.target.all_revision_ids())
 
3514
            result_set = set(source_ids).difference(target_ids)
 
3515
        return self.source.revision_ids_to_search_result(result_set)
 
3516
 
 
3517
 
3651
3518
class InterDifferingSerializer(InterRepository):
3652
3519
 
3653
3520
    @classmethod
3690
3557
        """Get the parent keys for a given root id."""
3691
3558
        root_id, rev_id = root_key
3692
3559
        # Include direct parents of the revision, but only if they used
3693
 
        # the same root_id and are heads.
 
3560
        # the same root_id.
3694
3561
        parent_keys = []
3695
3562
        for parent_id in parent_map[rev_id]:
3696
3563
            if parent_id == _mod_revision.NULL_REVISION:
3710
3577
                self._revision_id_to_root_id[parent_id] = None
3711
3578
            else:
3712
3579
                parent_root_id = self._revision_id_to_root_id[parent_id]
3713
 
            if root_id == parent_root_id:
3714
 
                # With stacking we _might_ want to refer to a non-local
3715
 
                # revision, but this code path only applies when we have the
3716
 
                # full content available, so ghosts really are ghosts, not just
3717
 
                # the edge of local data.
3718
 
                parent_keys.append((parent_id,))
3719
 
            else:
3720
 
                # root_id may be in the parent anyway.
3721
 
                try:
3722
 
                    tree = self.source.revision_tree(parent_id)
3723
 
                except errors.NoSuchRevision:
3724
 
                    # ghost, can't refer to it.
3725
 
                    pass
3726
 
                else:
3727
 
                    try:
3728
 
                        parent_keys.append((tree.inventory[root_id].revision,))
3729
 
                    except errors.NoSuchId:
3730
 
                        # not in the tree
3731
 
                        pass
3732
 
        g = graph.Graph(self.source.revisions)
3733
 
        heads = g.heads(parent_keys)
3734
 
        selected_keys = []
3735
 
        for key in parent_keys:
3736
 
            if key in heads and key not in selected_keys:
3737
 
                selected_keys.append(key)
3738
 
        return tuple([(root_id,)+ key for key in selected_keys])
 
3580
            if root_id == parent_root_id or parent_root_id is None:
 
3581
                parent_keys.append((root_id, parent_id))
 
3582
        return tuple(parent_keys)
3739
3583
 
3740
3584
    def _new_root_data_stream(self, root_keys_to_create, parent_map):
3741
3585
        for root_key in root_keys_to_create:
3839
3683
 
3840
3684
        :param revision_ids: The list of revisions to fetch. Must be in
3841
3685
            topological order.
3842
 
        :param pb: A ProgressTask
 
3686
        :param pb: A ProgressBar
3843
3687
        :return: None
3844
3688
        """
3845
3689
        basis_id, basis_tree = self._get_basis(revision_ids[0])
3847
3691
        cache = lru_cache.LRUCache(100)
3848
3692
        cache[basis_id] = basis_tree
3849
3693
        del basis_tree # We don't want to hang on to it here
3850
 
        hints = []
3851
3694
        for offset in range(0, len(revision_ids), batch_size):
3852
3695
            self.target.start_write_group()
3853
3696
            try:
3859
3702
                self.target.abort_write_group()
3860
3703
                raise
3861
3704
            else:
3862
 
                hint = self.target.commit_write_group()
3863
 
                if hint:
3864
 
                    hints.extend(hint)
3865
 
        if hints and self.target._format.pack_compresses:
3866
 
            self.target.pack(hint=hints)
 
3705
                self.target.commit_write_group()
3867
3706
        pb.update('Transferring revisions', len(revision_ids),
3868
3707
                  len(revision_ids))
3869
3708
 
3933
3772
InterRepository.register_optimiser(InterSameDataRepository)
3934
3773
InterRepository.register_optimiser(InterWeaveRepo)
3935
3774
InterRepository.register_optimiser(InterKnitRepo)
 
3775
InterRepository.register_optimiser(InterPackRepo)
3936
3776
 
3937
3777
 
3938
3778
class CopyConverter(object):
4019
3859
 
4020
3860
class _VersionedFileChecker(object):
4021
3861
 
4022
 
    def __init__(self, repository, text_key_references=None, ancestors=None):
 
3862
    def __init__(self, repository, text_key_references=None):
4023
3863
        self.repository = repository
4024
3864
        self.text_index = self.repository._generate_text_key_index(
4025
 
            text_key_references=text_key_references, ancestors=ancestors)
 
3865
            text_key_references=text_key_references)
4026
3866
 
4027
3867
    def calculate_file_version_parents(self, text_key):
4028
3868
        """Calculate the correct parents for a file version according to
4046
3886
            revision_id) tuples for versions that are present in this versioned
4047
3887
            file, but not used by the corresponding inventory.
4048
3888
        """
4049
 
        local_progress = None
4050
 
        if progress_bar is None:
4051
 
            local_progress = ui.ui_factory.nested_progress_bar()
4052
 
            progress_bar = local_progress
4053
 
        try:
4054
 
            return self._check_file_version_parents(texts, progress_bar)
4055
 
        finally:
4056
 
            if local_progress:
4057
 
                local_progress.finished()
4058
 
 
4059
 
    def _check_file_version_parents(self, texts, progress_bar):
4060
 
        """See check_file_version_parents."""
4061
3889
        wrong_parents = {}
4062
3890
        self.file_ids = set([file_id for file_id, _ in
4063
3891
            self.text_index.iterkeys()])
4072
3900
        text_keys = self.repository.texts.keys()
4073
3901
        unused_keys = frozenset(text_keys) - set(self.text_index)
4074
3902
        for num, key in enumerate(self.text_index.iterkeys()):
4075
 
            progress_bar.update('checking text graph', num, n_versions)
 
3903
            if progress_bar is not None:
 
3904
                progress_bar.update('checking text graph', num, n_versions)
4076
3905
            correct_parents = self.calculate_file_version_parents(key)
4077
3906
            try:
4078
3907
                knit_parents = parent_map[key]
4127
3956
        try:
4128
3957
            if resume_tokens:
4129
3958
                self.target_repo.resume_write_group(resume_tokens)
4130
 
                is_resume = True
4131
3959
            else:
4132
3960
                self.target_repo.start_write_group()
4133
 
                is_resume = False
4134
3961
            try:
4135
3962
                # locked_insert_stream performs a commit|suspend.
4136
 
                return self._locked_insert_stream(stream, src_format, is_resume)
 
3963
                return self._locked_insert_stream(stream, src_format)
4137
3964
            except:
4138
3965
                self.target_repo.abort_write_group(suppress_errors=True)
4139
3966
                raise
4140
3967
        finally:
4141
3968
            self.target_repo.unlock()
4142
3969
 
4143
 
    def _locked_insert_stream(self, stream, src_format, is_resume):
 
3970
    def _locked_insert_stream(self, stream, src_format):
4144
3971
        to_serializer = self.target_repo._format._serializer
4145
3972
        src_serializer = src_format._serializer
4146
 
        new_pack = None
4147
3973
        if to_serializer == src_serializer:
4148
3974
            # If serializers match and the target is a pack repository, set the
4149
3975
            # write cache size on the new pack.  This avoids poor performance
4190
4016
                self.target_repo.signatures.insert_record_stream(substream)
4191
4017
            else:
4192
4018
                raise AssertionError('kaboom! %s' % (substream_type,))
4193
 
        # Done inserting data, and the missing_keys calculations will try to
4194
 
        # read back from the inserted data, so flush the writes to the new pack
4195
 
        # (if this is pack format).
4196
 
        if new_pack is not None:
4197
 
            new_pack._write_data('', flush=True)
4198
4019
        # Find all the new revisions (including ones from resume_tokens)
4199
 
        missing_keys = self.target_repo.get_missing_parent_inventories(
4200
 
            check_for_missing_texts=is_resume)
 
4020
        missing_keys = self.target_repo.get_missing_parent_inventories()
4201
4021
        try:
4202
4022
            for prefix, versioned_file in (
4203
4023
                ('texts', self.target_repo.texts),
4204
4024
                ('inventories', self.target_repo.inventories),
4205
4025
                ('revisions', self.target_repo.revisions),
4206
4026
                ('signatures', self.target_repo.signatures),
4207
 
                ('chk_bytes', self.target_repo.chk_bytes),
4208
4027
                ):
4209
 
                if versioned_file is None:
4210
 
                    continue
4211
4028
                missing_keys.update((prefix,) + key for key in
4212
4029
                    versioned_file.get_missing_compression_parent_keys())
4213
4030
        except NotImplementedError:
4222
4039
                # missing keys can handle suspending a write group).
4223
4040
                write_group_tokens = self.target_repo.suspend_write_group()
4224
4041
                return write_group_tokens, missing_keys
4225
 
        hint = self.target_repo.commit_write_group()
4226
 
        if (to_serializer != src_serializer and
4227
 
            self.target_repo._format.pack_compresses):
4228
 
            self.target_repo.pack(hint=hint)
 
4042
        self.target_repo.commit_write_group()
4229
4043
        return [], set()
4230
4044
 
4231
4045
    def _extract_and_insert_inventories(self, substream, serializer):
4363
4177
        keys['texts'] = set()
4364
4178
        keys['revisions'] = set()
4365
4179
        keys['inventories'] = set()
4366
 
        keys['chk_bytes'] = set()
4367
4180
        keys['signatures'] = set()
4368
4181
        for key in missing_keys:
4369
4182
            keys[key[0]].add(key[1:])
4376
4189
                    keys['revisions'],))
4377
4190
        for substream_kind, keys in keys.iteritems():
4378
4191
            vf = getattr(self.from_repository, substream_kind)
4379
 
            if vf is None and keys:
4380
 
                    raise AssertionError(
4381
 
                        "cannot fill in keys for a versioned file we don't"
4382
 
                        " have: %s needs %s" % (substream_kind, keys))
4383
 
            if not keys:
4384
 
                # No need to stream something we don't have
4385
 
                continue
4386
4192
            # Ask for full texts always so that we don't need more round trips
4387
4193
            # after this stream.
4388
 
            # Some of the missing keys are genuinely ghosts, so filter absent
4389
 
            # records. The Sink is responsible for doing another check to
4390
 
            # ensure that ghosts don't introduce missing data for future
4391
 
            # fetches.
4392
 
            stream = versionedfile.filter_absent(vf.get_record_stream(keys,
4393
 
                self.to_format._fetch_order, True))
 
4194
            stream = vf.get_record_stream(keys,
 
4195
                self.to_format._fetch_order, True)
4394
4196
            yield substream_kind, stream
4395
4197
 
4396
4198
    def inventory_fetch_order(self):
4527
4329
            yield versionedfile.FulltextContentFactory(
4528
4330
                key, parent_keys, None, as_bytes)
4529
4331
 
4530
 
 
4531
 
def _iter_for_revno(repo, partial_history_cache, stop_index=None,
4532
 
                    stop_revision=None):
4533
 
    """Extend the partial history to include a given index
4534
 
 
4535
 
    If a stop_index is supplied, stop when that index has been reached.
4536
 
    If a stop_revision is supplied, stop when that revision is
4537
 
    encountered.  Otherwise, stop when the beginning of history is
4538
 
    reached.
4539
 
 
4540
 
    :param stop_index: The index which should be present.  When it is
4541
 
        present, history extension will stop.
4542
 
    :param stop_revision: The revision id which should be present.  When
4543
 
        it is encountered, history extension will stop.
4544
 
    """
4545
 
    start_revision = partial_history_cache[-1]
4546
 
    iterator = repo.iter_reverse_revision_history(start_revision)
4547
 
    try:
4548
 
        #skip the last revision in the list
4549
 
        iterator.next()
4550
 
        while True:
4551
 
            if (stop_index is not None and
4552
 
                len(partial_history_cache) > stop_index):
4553
 
                break
4554
 
            if partial_history_cache[-1] == stop_revision:
4555
 
                break
4556
 
            revision_id = iterator.next()
4557
 
            partial_history_cache.append(revision_id)
4558
 
    except StopIteration:
4559
 
        # No more history
4560
 
        return
4561