~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-01-14 00:01:32 UTC
  • mfrom: (4957.1.1 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100114000132-3p3rabnonjw3gzqb
(jam) Merge bzr.stable, bringing in bug fixes #175839, #504390

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
24
24
    bzrdir,
25
25
    check,
26
26
    chk_map,
 
27
    config,
27
28
    debug,
28
29
    errors,
 
30
    fetch as _mod_fetch,
29
31
    fifo_cache,
30
32
    generate_ids,
31
33
    gpg,
32
34
    graph,
33
35
    inventory,
 
36
    inventory_delta,
34
37
    lazy_regex,
35
38
    lockable_files,
36
39
    lockdir,
38
41
    osutils,
39
42
    revision as _mod_revision,
40
43
    symbol_versioning,
 
44
    trace,
41
45
    tsort,
42
46
    ui,
43
47
    versionedfile,
48
52
from bzrlib.testament import Testament
49
53
""")
50
54
 
51
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
55
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
52
56
from bzrlib.inter import InterObject
53
57
from bzrlib.inventory import (
54
58
    Inventory,
56
60
    ROOT_ID,
57
61
    entry_factory,
58
62
    )
 
63
from bzrlib.lock import _RelockDebugMixin
59
64
from bzrlib import registry
60
 
from bzrlib.symbol_versioning import (
61
 
        deprecated_method,
62
 
        )
63
65
from bzrlib.trace import (
64
66
    log_exception_quietly, note, mutter, mutter_callsite, warning)
65
67
 
207
209
            # an inventory delta was accumulated without creating a new
208
210
            # inventory.
209
211
            basis_id = self.basis_delta_revision
210
 
            self.inv_sha1 = self.repository.add_inventory_by_delta(
 
212
            # We ignore the 'inventory' returned by add_inventory_by_delta
 
213
            # because self.new_inventory is used to hint to the rest of the
 
214
            # system what code path was taken
 
215
            self.inv_sha1, _ = self.repository.add_inventory_by_delta(
211
216
                basis_id, self._basis_delta, self._new_revision_id,
212
217
                self.parents)
213
218
        else:
467
472
            if content_summary[2] is None:
468
473
                raise ValueError("Files must not have executable = None")
469
474
            if not store:
470
 
                if (# if the file length changed we have to store:
471
 
                    parent_entry.text_size != content_summary[1] or
472
 
                    # if the exec bit has changed we have to store:
 
475
                # We can't trust a check of the file length because of content
 
476
                # filtering...
 
477
                if (# if the exec bit has changed we have to store:
473
478
                    parent_entry.executable != content_summary[2]):
474
479
                    store = True
475
480
                elif parent_entry.text_sha1 == content_summary[3]:
494
499
            ie.executable = content_summary[2]
495
500
            file_obj, stat_value = tree.get_file_with_stat(ie.file_id, path)
496
501
            try:
497
 
                lines = file_obj.readlines()
 
502
                text = file_obj.read()
498
503
            finally:
499
504
                file_obj.close()
500
505
            try:
501
506
                ie.text_sha1, ie.text_size = self._add_text_to_weave(
502
 
                    ie.file_id, lines, heads, nostore_sha)
 
507
                    ie.file_id, text, heads, nostore_sha)
503
508
                # Let the caller know we generated a stat fingerprint.
504
509
                fingerprint = (ie.text_sha1, stat_value)
505
510
            except errors.ExistingContent:
517
522
                # carry over:
518
523
                ie.revision = parent_entry.revision
519
524
                return self._get_delta(ie, basis_inv, path), False, None
520
 
            lines = []
521
 
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
525
            self._add_text_to_weave(ie.file_id, '', heads, None)
522
526
        elif kind == 'symlink':
523
527
            current_link_target = content_summary[3]
524
528
            if not store:
532
536
                ie.symlink_target = parent_entry.symlink_target
533
537
                return self._get_delta(ie, basis_inv, path), False, None
534
538
            ie.symlink_target = current_link_target
535
 
            lines = []
536
 
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
539
            self._add_text_to_weave(ie.file_id, '', heads, None)
537
540
        elif kind == 'tree-reference':
538
541
            if not store:
539
542
                if content_summary[3] != parent_entry.reference_revision:
544
547
                ie.revision = parent_entry.revision
545
548
                return self._get_delta(ie, basis_inv, path), False, None
546
549
            ie.reference_revision = content_summary[3]
547
 
            lines = []
548
 
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
550
            if ie.reference_revision is None:
 
551
                raise AssertionError("invalid content_summary for nested tree: %r"
 
552
                    % (content_summary,))
 
553
            self._add_text_to_weave(ie.file_id, '', heads, None)
549
554
        else:
550
555
            raise NotImplementedError('unknown kind')
551
556
        ie.revision = self._new_revision_id
745
750
                        entry.executable = True
746
751
                    else:
747
752
                        entry.executable = False
748
 
                    if (carry_over_possible and 
 
753
                    if (carry_over_possible and
749
754
                        parent_entry.executable == entry.executable):
750
755
                            # Check the file length, content hash after reading
751
756
                            # the file.
754
759
                        nostore_sha = None
755
760
                    file_obj, stat_value = tree.get_file_with_stat(file_id, change[1][1])
756
761
                    try:
757
 
                        lines = file_obj.readlines()
 
762
                        text = file_obj.read()
758
763
                    finally:
759
764
                        file_obj.close()
760
765
                    try:
761
766
                        entry.text_sha1, entry.text_size = self._add_text_to_weave(
762
 
                            file_id, lines, heads, nostore_sha)
 
767
                            file_id, text, heads, nostore_sha)
763
768
                        yield file_id, change[1][1], (entry.text_sha1, stat_value)
764
769
                    except errors.ExistingContent:
765
770
                        # No content change against a carry_over parent
774
779
                        parent_entry.symlink_target == entry.symlink_target):
775
780
                        carried_over = True
776
781
                    else:
777
 
                        self._add_text_to_weave(change[0], [], heads, None)
 
782
                        self._add_text_to_weave(change[0], '', heads, None)
778
783
                elif kind == 'directory':
779
784
                    if carry_over_possible:
780
785
                        carried_over = True
782
787
                        # Nothing to set on the entry.
783
788
                        # XXX: split into the Root and nonRoot versions.
784
789
                        if change[1][1] != '' or self.repository.supports_rich_root():
785
 
                            self._add_text_to_weave(change[0], [], heads, None)
 
790
                            self._add_text_to_weave(change[0], '', heads, None)
786
791
                elif kind == 'tree-reference':
787
792
                    if not self.repository._format.supports_tree_reference:
788
793
                        # This isn't quite sane as an error, but we shouldn't
791
796
                        # references.
792
797
                        raise errors.UnsupportedOperation(tree.add_reference,
793
798
                            self.repository)
794
 
                    entry.reference_revision = \
795
 
                        tree.get_reference_revision(change[0])
 
799
                    reference_revision = tree.get_reference_revision(change[0])
 
800
                    entry.reference_revision = reference_revision
796
801
                    if (carry_over_possible and
797
802
                        parent_entry.reference_revision == reference_revision):
798
803
                        carried_over = True
799
804
                    else:
800
 
                        self._add_text_to_weave(change[0], [], heads, None)
 
805
                        self._add_text_to_weave(change[0], '', heads, None)
801
806
                else:
802
807
                    raise AssertionError('unknown kind %r' % kind)
803
808
                if not carried_over:
812
817
                seen_root = True
813
818
        self.new_inventory = None
814
819
        if len(inv_delta):
 
820
            # This should perhaps be guarded by a check that the basis we
 
821
            # commit against is the basis for the commit and if not do a delta
 
822
            # against the basis.
815
823
            self._any_changes = True
816
824
        if not seen_root:
817
825
            # housekeeping root entry changes do not affect no-change commits.
818
826
            self._require_root_change(tree)
819
827
        self.basis_delta_revision = basis_revision_id
820
828
 
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]
 
829
    def _add_text_to_weave(self, file_id, new_text, parents, nostore_sha):
 
830
        parent_keys = tuple([(file_id, parent) for parent in parents])
 
831
        return self.repository.texts._add_text(
 
832
            (file_id, self._new_revision_id), parent_keys, new_text,
 
833
            nostore_sha=nostore_sha, random_id=self.random_revid)[0:2]
832
834
 
833
835
 
834
836
class RootCommitBuilder(CommitBuilder):
860
862
######################################################################
861
863
# Repositories
862
864
 
863
 
class Repository(object):
 
865
 
 
866
class Repository(_RelockDebugMixin):
864
867
    """Repository holding history for one or more branches.
865
868
 
866
869
    The repository holds and retrieves historical information including
935
938
        """
936
939
        if self._write_group is not self.get_transaction():
937
940
            # has an unlock or relock occured ?
 
941
            if suppress_errors:
 
942
                mutter(
 
943
                '(suppressed) mismatched lock context and write group. %r, %r',
 
944
                self._write_group, self.get_transaction())
 
945
                return
938
946
            raise errors.BzrError(
939
947
                'mismatched lock context and write group. %r, %r' %
940
948
                (self._write_group, self.get_transaction()))
969
977
        """
970
978
        if not self._format.supports_external_lookups:
971
979
            raise errors.UnstackableRepositoryFormat(self._format, self.base)
 
980
        if self.is_locked():
 
981
            # This repository will call fallback.unlock() when we transition to
 
982
            # the unlocked state, so we make sure to increment the lock count
 
983
            repository.lock_read()
972
984
        self._check_fallback_repository(repository)
973
985
        self._fallback_repositories.append(repository)
974
986
        self.texts.add_fallback_versioned_files(repository.texts)
1023
1035
                               parents, basis_inv=None, propagate_caches=False):
1024
1036
        """Add a new inventory expressed as a delta against another revision.
1025
1037
 
 
1038
        See the inventory developers documentation for the theory behind
 
1039
        inventory deltas.
 
1040
 
1026
1041
        :param basis_revision_id: The inventory id the delta was created
1027
1042
            against. (This does not have to be a direct parent.)
1028
1043
        :param delta: The inventory delta (see Inventory.apply_delta for
1067
1082
        check_content=True):
1068
1083
        """Store lines in inv_vf and return the sha1 of the inventory."""
1069
1084
        parents = [(parent,) for parent in parents]
1070
 
        return self.inventories.add_lines((revision_id,), parents, lines,
 
1085
        result = self.inventories.add_lines((revision_id,), parents, lines,
1071
1086
            check_content=check_content)[0]
 
1087
        self.inventories._access.flush()
 
1088
        return result
1072
1089
 
1073
1090
    def add_revision(self, revision_id, rev, inv=None, config=None):
1074
1091
        """Add rev to the revision store as revision_id.
1150
1167
        # The old API returned a list, should this actually be a set?
1151
1168
        return parent_map.keys()
1152
1169
 
 
1170
    def _check_inventories(self, checker):
 
1171
        """Check the inventories found from the revision scan.
 
1172
        
 
1173
        This is responsible for verifying the sha1 of inventories and
 
1174
        creating a pending_keys set that covers data referenced by inventories.
 
1175
        """
 
1176
        bar = ui.ui_factory.nested_progress_bar()
 
1177
        try:
 
1178
            self._do_check_inventories(checker, bar)
 
1179
        finally:
 
1180
            bar.finished()
 
1181
 
 
1182
    def _do_check_inventories(self, checker, bar):
 
1183
        """Helper for _check_inventories."""
 
1184
        revno = 0
 
1185
        keys = {'chk_bytes':set(), 'inventories':set(), 'texts':set()}
 
1186
        kinds = ['chk_bytes', 'texts']
 
1187
        count = len(checker.pending_keys)
 
1188
        bar.update("inventories", 0, 2)
 
1189
        current_keys = checker.pending_keys
 
1190
        checker.pending_keys = {}
 
1191
        # Accumulate current checks.
 
1192
        for key in current_keys:
 
1193
            if key[0] != 'inventories' and key[0] not in kinds:
 
1194
                checker._report_items.append('unknown key type %r' % (key,))
 
1195
            keys[key[0]].add(key[1:])
 
1196
        if keys['inventories']:
 
1197
            # NB: output order *should* be roughly sorted - topo or
 
1198
            # inverse topo depending on repository - either way decent
 
1199
            # to just delta against. However, pre-CHK formats didn't
 
1200
            # try to optimise inventory layout on disk. As such the
 
1201
            # pre-CHK code path does not use inventory deltas.
 
1202
            last_object = None
 
1203
            for record in self.inventories.check(keys=keys['inventories']):
 
1204
                if record.storage_kind == 'absent':
 
1205
                    checker._report_items.append(
 
1206
                        'Missing inventory {%s}' % (record.key,))
 
1207
                else:
 
1208
                    last_object = self._check_record('inventories', record,
 
1209
                        checker, last_object,
 
1210
                        current_keys[('inventories',) + record.key])
 
1211
            del keys['inventories']
 
1212
        else:
 
1213
            return
 
1214
        bar.update("texts", 1)
 
1215
        while (checker.pending_keys or keys['chk_bytes']
 
1216
            or keys['texts']):
 
1217
            # Something to check.
 
1218
            current_keys = checker.pending_keys
 
1219
            checker.pending_keys = {}
 
1220
            # Accumulate current checks.
 
1221
            for key in current_keys:
 
1222
                if key[0] not in kinds:
 
1223
                    checker._report_items.append('unknown key type %r' % (key,))
 
1224
                keys[key[0]].add(key[1:])
 
1225
            # Check the outermost kind only - inventories || chk_bytes || texts
 
1226
            for kind in kinds:
 
1227
                if keys[kind]:
 
1228
                    last_object = None
 
1229
                    for record in getattr(self, kind).check(keys=keys[kind]):
 
1230
                        if record.storage_kind == 'absent':
 
1231
                            checker._report_items.append(
 
1232
                                'Missing %s {%s}' % (kind, record.key,))
 
1233
                        else:
 
1234
                            last_object = self._check_record(kind, record,
 
1235
                                checker, last_object, current_keys[(kind,) + record.key])
 
1236
                    keys[kind] = set()
 
1237
                    break
 
1238
 
 
1239
    def _check_record(self, kind, record, checker, last_object, item_data):
 
1240
        """Check a single text from this repository."""
 
1241
        if kind == 'inventories':
 
1242
            rev_id = record.key[0]
 
1243
            inv = self.deserialise_inventory(rev_id,
 
1244
                record.get_bytes_as('fulltext'))
 
1245
            if last_object is not None:
 
1246
                delta = inv._make_delta(last_object)
 
1247
                for old_path, path, file_id, ie in delta:
 
1248
                    if ie is None:
 
1249
                        continue
 
1250
                    ie.check(checker, rev_id, inv)
 
1251
            else:
 
1252
                for path, ie in inv.iter_entries():
 
1253
                    ie.check(checker, rev_id, inv)
 
1254
            if self._format.fast_deltas:
 
1255
                return inv
 
1256
        elif kind == 'chk_bytes':
 
1257
            # No code written to check chk_bytes for this repo format.
 
1258
            checker._report_items.append(
 
1259
                'unsupported key type chk_bytes for %s' % (record.key,))
 
1260
        elif kind == 'texts':
 
1261
            self._check_text(record, checker, item_data)
 
1262
        else:
 
1263
            checker._report_items.append(
 
1264
                'unknown key type %s for %s' % (kind, record.key))
 
1265
 
 
1266
    def _check_text(self, record, checker, item_data):
 
1267
        """Check a single text."""
 
1268
        # Check it is extractable.
 
1269
        # TODO: check length.
 
1270
        if record.storage_kind == 'chunked':
 
1271
            chunks = record.get_bytes_as(record.storage_kind)
 
1272
            sha1 = osutils.sha_strings(chunks)
 
1273
            length = sum(map(len, chunks))
 
1274
        else:
 
1275
            content = record.get_bytes_as('fulltext')
 
1276
            sha1 = osutils.sha_string(content)
 
1277
            length = len(content)
 
1278
        if item_data and sha1 != item_data[1]:
 
1279
            checker._report_items.append(
 
1280
                'sha1 mismatch: %s has sha1 %s expected %s referenced by %s' %
 
1281
                (record.key, sha1, item_data[1], item_data[2]))
 
1282
 
1153
1283
    @staticmethod
1154
1284
    def create(a_bzrdir):
1155
1285
        """Construct the current default format repository in a_bzrdir."""
1176
1306
        self._reconcile_does_inventory_gc = True
1177
1307
        self._reconcile_fixes_text_parents = False
1178
1308
        self._reconcile_backsup_inventory = True
1179
 
        # not right yet - should be more semantically clear ?
1180
 
        #
1181
 
        # TODO: make sure to construct the right store classes, etc, depending
1182
 
        # on whether escaping is required.
1183
 
        self._warn_if_deprecated()
1184
1309
        self._write_group = None
1185
1310
        # Additional places to query for data.
1186
1311
        self._fallback_repositories = []
1187
1312
        # An InventoryEntry cache, used during deserialization
1188
1313
        self._inventory_entry_cache = fifo_cache.FIFOCache(10*1024)
 
1314
        # Is it safe to return inventory entries directly from the entry cache,
 
1315
        # rather copying them?
 
1316
        self._safe_to_return_from_cache = False
1189
1317
 
1190
1318
    def __repr__(self):
1191
 
        return '%s(%r)' % (self.__class__.__name__,
1192
 
                           self.base)
 
1319
        if self._fallback_repositories:
 
1320
            return '%s(%r, fallback_repositories=%r)' % (
 
1321
                self.__class__.__name__,
 
1322
                self.base,
 
1323
                self._fallback_repositories)
 
1324
        else:
 
1325
            return '%s(%r)' % (self.__class__.__name__,
 
1326
                               self.base)
 
1327
 
 
1328
    def _has_same_fallbacks(self, other_repo):
 
1329
        """Returns true if the repositories have the same fallbacks."""
 
1330
        my_fb = self._fallback_repositories
 
1331
        other_fb = other_repo._fallback_repositories
 
1332
        if len(my_fb) != len(other_fb):
 
1333
            return False
 
1334
        for f, g in zip(my_fb, other_fb):
 
1335
            if not f.has_same_location(g):
 
1336
                return False
 
1337
        return True
1193
1338
 
1194
1339
    def has_same_location(self, other):
1195
1340
        """Returns a boolean indicating if this repository is at the same
1240
1385
        """
1241
1386
        locked = self.is_locked()
1242
1387
        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
1388
        if not locked:
 
1389
            self._warn_if_deprecated()
 
1390
            self._note_lock('w')
 
1391
            for repo in self._fallback_repositories:
 
1392
                # Writes don't affect fallback repos
 
1393
                repo.lock_read()
1247
1394
            self._refresh_data()
1248
1395
        return result
1249
1396
 
1250
1397
    def lock_read(self):
1251
1398
        locked = self.is_locked()
1252
1399
        self.control_files.lock_read()
1253
 
        for repo in self._fallback_repositories:
1254
 
            repo.lock_read()
1255
1400
        if not locked:
 
1401
            self._warn_if_deprecated()
 
1402
            self._note_lock('r')
 
1403
            for repo in self._fallback_repositories:
 
1404
                repo.lock_read()
1256
1405
            self._refresh_data()
1257
1406
 
1258
1407
    def get_physical_lock_status(self):
1403
1552
        """Commit the contents accrued within the current write group.
1404
1553
 
1405
1554
        :seealso: start_write_group.
 
1555
        
 
1556
        :return: it may return an opaque hint that can be passed to 'pack'.
1406
1557
        """
1407
1558
        if self._write_group is not self.get_transaction():
1408
1559
            # has an unlock or relock occured ?
1409
1560
            raise errors.BzrError('mismatched lock context %r and '
1410
1561
                'write group %r.' %
1411
1562
                (self.get_transaction(), self._write_group))
1412
 
        self._commit_write_group()
 
1563
        result = self._commit_write_group()
1413
1564
        self._write_group = None
 
1565
        return result
1414
1566
 
1415
1567
    def _commit_write_group(self):
1416
1568
        """Template method for per-repository write group cleanup.
1424
1576
    def suspend_write_group(self):
1425
1577
        raise errors.UnsuspendableWriteGroup(self)
1426
1578
 
 
1579
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
 
1580
        """Return the keys of missing inventory parents for revisions added in
 
1581
        this write group.
 
1582
 
 
1583
        A revision is not complete if the inventory delta for that revision
 
1584
        cannot be calculated.  Therefore if the parent inventories of a
 
1585
        revision are not present, the revision is incomplete, and e.g. cannot
 
1586
        be streamed by a smart server.  This method finds missing inventory
 
1587
        parents for revisions added in this write group.
 
1588
        """
 
1589
        if not self._format.supports_external_lookups:
 
1590
            # This is only an issue for stacked repositories
 
1591
            return set()
 
1592
        if not self.is_in_write_group():
 
1593
            raise AssertionError('not in a write group')
 
1594
 
 
1595
        # XXX: We assume that every added revision already has its
 
1596
        # corresponding inventory, so we only check for parent inventories that
 
1597
        # might be missing, rather than all inventories.
 
1598
        parents = set(self.revisions._index.get_missing_parents())
 
1599
        parents.discard(_mod_revision.NULL_REVISION)
 
1600
        unstacked_inventories = self.inventories._index
 
1601
        present_inventories = unstacked_inventories.get_parent_map(
 
1602
            key[-1:] for key in parents)
 
1603
        parents.difference_update(present_inventories)
 
1604
        if len(parents) == 0:
 
1605
            # No missing parent inventories.
 
1606
            return set()
 
1607
        if not check_for_missing_texts:
 
1608
            return set(('inventories', rev_id) for (rev_id,) in parents)
 
1609
        # Ok, now we have a list of missing inventories.  But these only matter
 
1610
        # if the inventories that reference them are missing some texts they
 
1611
        # appear to introduce.
 
1612
        # XXX: Texts referenced by all added inventories need to be present,
 
1613
        # but at the moment we're only checking for texts referenced by
 
1614
        # inventories at the graph's edge.
 
1615
        key_deps = self.revisions._index._key_dependencies
 
1616
        key_deps.satisfy_refs_for_keys(present_inventories)
 
1617
        referrers = frozenset(r[0] for r in key_deps.get_referrers())
 
1618
        file_ids = self.fileids_altered_by_revision_ids(referrers)
 
1619
        missing_texts = set()
 
1620
        for file_id, version_ids in file_ids.iteritems():
 
1621
            missing_texts.update(
 
1622
                (file_id, version_id) for version_id in version_ids)
 
1623
        present_texts = self.texts.get_parent_map(missing_texts)
 
1624
        missing_texts.difference_update(present_texts)
 
1625
        if not missing_texts:
 
1626
            # No texts are missing, so all revisions and their deltas are
 
1627
            # reconstructable.
 
1628
            return set()
 
1629
        # Alternatively the text versions could be returned as the missing
 
1630
        # keys, but this is likely to be less data.
 
1631
        missing_keys = set(('inventories', rev_id) for (rev_id,) in parents)
 
1632
        return missing_keys
 
1633
 
1427
1634
    def refresh_data(self):
1428
1635
        """Re-read any data needed to to synchronise with disk.
1429
1636
 
1478
1685
            raise errors.InternalBzrError(
1479
1686
                "May not fetch while in a write group.")
1480
1687
        # fast path same-url fetch operations
1481
 
        if self.has_same_location(source) and fetch_spec is None:
 
1688
        # TODO: lift out to somewhere common with RemoteRepository
 
1689
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
 
1690
        if (self.has_same_location(source)
 
1691
            and fetch_spec is None
 
1692
            and self._has_same_fallbacks(source)):
1482
1693
            # check that last_revision is in 'from' and then return a
1483
1694
            # no-operation.
1484
1695
            if (revision_id is not None and
1509
1720
        :param revprops: Optional dictionary of revision properties.
1510
1721
        :param revision_id: Optional revision id.
1511
1722
        """
 
1723
        if self._fallback_repositories:
 
1724
            raise errors.BzrError("Cannot commit from a lightweight checkout "
 
1725
                "to a stacked branch. See "
 
1726
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1512
1727
        result = self._commit_builder_class(self, parents, config,
1513
1728
            timestamp, timezone, committer, revprops, revision_id)
1514
1729
        self.start_write_group()
1515
1730
        return result
1516
1731
 
 
1732
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1517
1733
    def unlock(self):
1518
1734
        if (self.control_files._lock_count == 1 and
1519
1735
            self.control_files._lock_mode == 'w'):
1525
1741
        self.control_files.unlock()
1526
1742
        if self.control_files._lock_count == 0:
1527
1743
            self._inventory_entry_cache.clear()
1528
 
        for repo in self._fallback_repositories:
1529
 
            repo.unlock()
 
1744
            for repo in self._fallback_repositories:
 
1745
                repo.unlock()
1530
1746
 
1531
1747
    @needs_read_lock
1532
1748
    def clone(self, a_bzrdir, revision_id=None):
1641
1857
 
1642
1858
    @needs_read_lock
1643
1859
    def get_revisions(self, revision_ids):
1644
 
        """Get many revisions at once."""
 
1860
        """Get many revisions at once.
 
1861
        
 
1862
        Repositories that need to check data on every revision read should 
 
1863
        subclass this method.
 
1864
        """
1645
1865
        return self._get_revisions(revision_ids)
1646
1866
 
1647
1867
    @needs_read_lock
1648
1868
    def _get_revisions(self, revision_ids):
1649
1869
        """Core work logic to get many revisions without sanity checks."""
1650
 
        for rev_id in revision_ids:
1651
 
            if not rev_id or not isinstance(rev_id, basestring):
1652
 
                raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
 
1870
        revs = {}
 
1871
        for revid, rev in self._iter_revisions(revision_ids):
 
1872
            if rev is None:
 
1873
                raise errors.NoSuchRevision(self, revid)
 
1874
            revs[revid] = rev
 
1875
        return [revs[revid] for revid in revision_ids]
 
1876
 
 
1877
    def _iter_revisions(self, revision_ids):
 
1878
        """Iterate over revision objects.
 
1879
 
 
1880
        :param revision_ids: An iterable of revisions to examine. None may be
 
1881
            passed to request all revisions known to the repository. Note that
 
1882
            not all repositories can find unreferenced revisions; for those
 
1883
            repositories only referenced ones will be returned.
 
1884
        :return: An iterator of (revid, revision) tuples. Absent revisions (
 
1885
            those asked for but not available) are returned as (revid, None).
 
1886
        """
 
1887
        if revision_ids is None:
 
1888
            revision_ids = self.all_revision_ids()
 
1889
        else:
 
1890
            for rev_id in revision_ids:
 
1891
                if not rev_id or not isinstance(rev_id, basestring):
 
1892
                    raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
1653
1893
        keys = [(key,) for key in revision_ids]
1654
1894
        stream = self.revisions.get_record_stream(keys, 'unordered', True)
1655
 
        revs = {}
1656
1895
        for record in stream:
 
1896
            revid = record.key[0]
1657
1897
            if record.storage_kind == 'absent':
1658
 
                raise errors.NoSuchRevision(self, record.key[0])
1659
 
            text = record.get_bytes_as('fulltext')
1660
 
            rev = self._serializer.read_revision_from_string(text)
1661
 
            revs[record.key[0]] = rev
1662
 
        return [revs[revid] for revid in revision_ids]
 
1898
                yield (revid, None)
 
1899
            else:
 
1900
                text = record.get_bytes_as('fulltext')
 
1901
                rev = self._serializer.read_revision_from_string(text)
 
1902
                yield (revid, rev)
1663
1903
 
1664
1904
    @needs_read_lock
1665
1905
    def get_revision_xml(self, revision_id):
1860
2100
                    yield line, revid
1861
2101
 
1862
2102
    def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
1863
 
        revision_ids):
 
2103
        revision_keys):
1864
2104
        """Helper routine for fileids_altered_by_revision_ids.
1865
2105
 
1866
2106
        This performs the translation of xml lines to revision ids.
1867
2107
 
1868
2108
        :param line_iterator: An iterator of lines, origin_version_id
1869
 
        :param revision_ids: The revision ids to filter for. This should be a
 
2109
        :param revision_keys: The revision ids to filter for. This should be a
1870
2110
            set or other type which supports efficient __contains__ lookups, as
1871
 
            the revision id from each parsed line will be looked up in the
1872
 
            revision_ids filter.
 
2111
            the revision key from each parsed line will be looked up in the
 
2112
            revision_keys filter.
1873
2113
        :return: a dictionary mapping altered file-ids to an iterable of
1874
2114
        revision_ids. Each altered file-ids has the exact revision_ids that
1875
2115
        altered it listed explicitly.
1876
2116
        """
1877
2117
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
1878
2118
                line_iterator).iterkeys())
1879
 
        # Note that revision_ids are revision keys.
1880
 
        parent_maps = self.revisions.get_parent_map(revision_ids)
1881
 
        parents = set()
1882
 
        map(parents.update, parent_maps.itervalues())
1883
 
        parents.difference_update(revision_ids)
 
2119
        parent_keys = self._find_parent_keys_of_revisions(revision_keys)
1884
2120
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
1885
 
            self._inventory_xml_lines_for_keys(parents)))
 
2121
            self._inventory_xml_lines_for_keys(parent_keys)))
1886
2122
        new_keys = seen - parent_seen
1887
2123
        result = {}
1888
2124
        setdefault = result.setdefault
1890
2126
            setdefault(key[0], set()).add(key[-1])
1891
2127
        return result
1892
2128
 
 
2129
    def _find_parent_ids_of_revisions(self, revision_ids):
 
2130
        """Find all parent ids that are mentioned in the revision graph.
 
2131
 
 
2132
        :return: set of revisions that are parents of revision_ids which are
 
2133
            not part of revision_ids themselves
 
2134
        """
 
2135
        parent_map = self.get_parent_map(revision_ids)
 
2136
        parent_ids = set()
 
2137
        map(parent_ids.update, parent_map.itervalues())
 
2138
        parent_ids.difference_update(revision_ids)
 
2139
        parent_ids.discard(_mod_revision.NULL_REVISION)
 
2140
        return parent_ids
 
2141
 
 
2142
    def _find_parent_keys_of_revisions(self, revision_keys):
 
2143
        """Similar to _find_parent_ids_of_revisions, but used with keys.
 
2144
 
 
2145
        :param revision_keys: An iterable of revision_keys.
 
2146
        :return: The parents of all revision_keys that are not already in
 
2147
            revision_keys
 
2148
        """
 
2149
        parent_map = self.revisions.get_parent_map(revision_keys)
 
2150
        parent_keys = set()
 
2151
        map(parent_keys.update, parent_map.itervalues())
 
2152
        parent_keys.difference_update(revision_keys)
 
2153
        parent_keys.discard(_mod_revision.NULL_REVISION)
 
2154
        return parent_keys
 
2155
 
1893
2156
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
1894
2157
        """Find the file ids and versions affected by revisions.
1895
2158
 
1997
2260
                batch_size]
1998
2261
            if not to_query:
1999
2262
                break
2000
 
            for rev_tree in self.revision_trees(to_query):
2001
 
                revision_id = rev_tree.get_revision_id()
 
2263
            for revision_id in to_query:
2002
2264
                parent_ids = ancestors[revision_id]
2003
2265
                for text_key in revision_keys[revision_id]:
2004
2266
                    pb.update("Calculating text parents", processed_texts)
2077
2339
        num_file_ids = len(file_ids)
2078
2340
        for file_id, altered_versions in file_ids.iteritems():
2079
2341
            if pb is not None:
2080
 
                pb.update("fetch texts", count, num_file_ids)
 
2342
                pb.update("Fetch texts", count, num_file_ids)
2081
2343
            count += 1
2082
2344
            yield ("file", file_id, altered_versions)
2083
2345
 
2104
2366
        """Get Inventory object by revision id."""
2105
2367
        return self.iter_inventories([revision_id]).next()
2106
2368
 
2107
 
    def iter_inventories(self, revision_ids):
 
2369
    def iter_inventories(self, revision_ids, ordering=None):
2108
2370
        """Get many inventories by revision_ids.
2109
2371
 
2110
2372
        This will buffer some or all of the texts used in constructing the
2112
2374
        time.
2113
2375
 
2114
2376
        :param revision_ids: The expected revision ids of the inventories.
 
2377
        :param ordering: optional ordering, e.g. 'topological'.  If not
 
2378
            specified, the order of revision_ids will be preserved (by
 
2379
            buffering if necessary).
2115
2380
        :return: An iterator of inventories.
2116
2381
        """
2117
2382
        if ((None in revision_ids)
2118
2383
            or (_mod_revision.NULL_REVISION in revision_ids)):
2119
2384
            raise ValueError('cannot get null revision inventory')
2120
 
        return self._iter_inventories(revision_ids)
 
2385
        return self._iter_inventories(revision_ids, ordering)
2121
2386
 
2122
 
    def _iter_inventories(self, revision_ids):
 
2387
    def _iter_inventories(self, revision_ids, ordering):
2123
2388
        """single-document based inventory iteration."""
2124
 
        for text, revision_id in self._iter_inventory_xmls(revision_ids):
 
2389
        inv_xmls = self._iter_inventory_xmls(revision_ids, ordering)
 
2390
        for text, revision_id in inv_xmls:
2125
2391
            yield self.deserialise_inventory(revision_id, text)
2126
2392
 
2127
 
    def _iter_inventory_xmls(self, revision_ids):
 
2393
    def _iter_inventory_xmls(self, revision_ids, ordering):
 
2394
        if ordering is None:
 
2395
            order_as_requested = True
 
2396
            ordering = 'unordered'
 
2397
        else:
 
2398
            order_as_requested = False
2128
2399
        keys = [(revision_id,) for revision_id in revision_ids]
2129
 
        stream = self.inventories.get_record_stream(keys, 'unordered', True)
 
2400
        if not keys:
 
2401
            return
 
2402
        if order_as_requested:
 
2403
            key_iter = iter(keys)
 
2404
            next_key = key_iter.next()
 
2405
        stream = self.inventories.get_record_stream(keys, ordering, True)
2130
2406
        text_chunks = {}
2131
2407
        for record in stream:
2132
2408
            if record.storage_kind != 'absent':
2133
 
                text_chunks[record.key] = record.get_bytes_as('chunked')
 
2409
                chunks = record.get_bytes_as('chunked')
 
2410
                if order_as_requested:
 
2411
                    text_chunks[record.key] = chunks
 
2412
                else:
 
2413
                    yield ''.join(chunks), record.key[-1]
2134
2414
            else:
2135
2415
                raise errors.NoSuchRevision(self, record.key)
2136
 
        for key in keys:
2137
 
            chunks = text_chunks.pop(key)
2138
 
            yield ''.join(chunks), key[-1]
 
2416
            if order_as_requested:
 
2417
                # Yield as many results as we can while preserving order.
 
2418
                while next_key in text_chunks:
 
2419
                    chunks = text_chunks.pop(next_key)
 
2420
                    yield ''.join(chunks), next_key[-1]
 
2421
                    try:
 
2422
                        next_key = key_iter.next()
 
2423
                    except StopIteration:
 
2424
                        # We still want to fully consume the get_record_stream,
 
2425
                        # just in case it is not actually finished at this point
 
2426
                        next_key = None
 
2427
                        break
2139
2428
 
2140
2429
    def deserialise_inventory(self, revision_id, xml):
2141
2430
        """Transform the xml into an inventory object.
2144
2433
        :param xml: A serialised inventory.
2145
2434
        """
2146
2435
        result = self._serializer.read_inventory_from_string(xml, revision_id,
2147
 
                    entry_cache=self._inventory_entry_cache)
 
2436
                    entry_cache=self._inventory_entry_cache,
 
2437
                    return_from_cache=self._safe_to_return_from_cache)
2148
2438
        if result.revision_id != revision_id:
2149
2439
            raise AssertionError('revision id mismatch %s != %s' % (
2150
2440
                result.revision_id, revision_id))
2162
2452
    @needs_read_lock
2163
2453
    def get_inventory_xml(self, revision_id):
2164
2454
        """Get inventory XML as a file object."""
2165
 
        texts = self._iter_inventory_xmls([revision_id])
 
2455
        texts = self._iter_inventory_xmls([revision_id], 'unordered')
2166
2456
        try:
2167
2457
            text, revision_id = texts.next()
2168
2458
        except StopIteration:
2175
2465
        """
2176
2466
        return self.get_revision(revision_id).inventory_sha1
2177
2467
 
 
2468
    def get_rev_id_for_revno(self, revno, known_pair):
 
2469
        """Return the revision id of a revno, given a later (revno, revid)
 
2470
        pair in the same history.
 
2471
 
 
2472
        :return: if found (True, revid).  If the available history ran out
 
2473
            before reaching the revno, then this returns
 
2474
            (False, (closest_revno, closest_revid)).
 
2475
        """
 
2476
        known_revno, known_revid = known_pair
 
2477
        partial_history = [known_revid]
 
2478
        distance_from_known = known_revno - revno
 
2479
        if distance_from_known < 0:
 
2480
            raise ValueError(
 
2481
                'requested revno (%d) is later than given known revno (%d)'
 
2482
                % (revno, known_revno))
 
2483
        try:
 
2484
            _iter_for_revno(
 
2485
                self, partial_history, stop_index=distance_from_known)
 
2486
        except errors.RevisionNotPresent, err:
 
2487
            if err.revision_id == known_revid:
 
2488
                # The start revision (known_revid) wasn't found.
 
2489
                raise
 
2490
            # This is a stacked repository with no fallbacks, or a there's a
 
2491
            # left-hand ghost.  Either way, even though the revision named in
 
2492
            # the error isn't in this repo, we know it's the next step in this
 
2493
            # left-hand history.
 
2494
            partial_history.append(err.revision_id)
 
2495
        if len(partial_history) <= distance_from_known:
 
2496
            # Didn't find enough history to get a revid for the revno.
 
2497
            earliest_revno = known_revno - len(partial_history) + 1
 
2498
            return (False, (earliest_revno, partial_history[-1]))
 
2499
        if len(partial_history) - 1 > distance_from_known:
 
2500
            raise AssertionError('_iter_for_revno returned too much history')
 
2501
        return (True, partial_history[-1])
 
2502
 
2178
2503
    def iter_reverse_revision_history(self, revision_id):
2179
2504
        """Iterate backwards through revision ids in the lefthand history
2180
2505
 
2186
2511
        while True:
2187
2512
            if next_id in (None, _mod_revision.NULL_REVISION):
2188
2513
                return
 
2514
            try:
 
2515
                parents = graph.get_parent_map([next_id])[next_id]
 
2516
            except KeyError:
 
2517
                raise errors.RevisionNotPresent(next_id, self)
2189
2518
            yield next_id
2190
 
            # Note: The following line may raise KeyError in the event of
2191
 
            # truncated history. We decided not to have a try:except:raise
2192
 
            # RevisionNotPresent here until we see a use for it, because of the
2193
 
            # cost in an inner loop that is by its very nature O(history).
2194
 
            # Robert Collins 20080326
2195
 
            parents = graph.get_parent_map([next_id])[next_id]
2196
2519
            if len(parents) == 0:
2197
2520
                return
2198
2521
            else:
2312
2635
            keys = tsort.topo_sort(parent_map)
2313
2636
        return [None] + list(keys)
2314
2637
 
2315
 
    def pack(self):
 
2638
    def pack(self, hint=None):
2316
2639
        """Compress the data within the repository.
2317
2640
 
2318
2641
        This operation only makes sense for some repository types. For other
2321
2644
        This stub method does not require a lock, but subclasses should use
2322
2645
        @needs_write_lock as this is a long running call its reasonable to
2323
2646
        implicitly lock for the user.
 
2647
 
 
2648
        :param hint: If not supplied, the whole repository is packed.
 
2649
            If supplied, the repository may use the hint parameter as a
 
2650
            hint for the parts of the repository to pack. A hint can be
 
2651
            obtained from the result of commit_write_group(). Out of
 
2652
            date hints are simply ignored, because concurrent operations
 
2653
            can obsolete them rapidly.
2324
2654
        """
2325
2655
 
2326
2656
    def get_transaction(self):
2327
2657
        return self.control_files.get_transaction()
2328
2658
 
2329
2659
    def get_parent_map(self, revision_ids):
2330
 
        """See graph._StackedParentsProvider.get_parent_map"""
 
2660
        """See graph.StackedParentsProvider.get_parent_map"""
2331
2661
        # revisions index works in keys; this just works in revisions
2332
2662
        # therefore wrap and unwrap
2333
2663
        query_keys = []
2342
2672
        for ((revision_id,), parent_keys) in \
2343
2673
                self.revisions.get_parent_map(query_keys).iteritems():
2344
2674
            if parent_keys:
2345
 
                result[revision_id] = tuple(parent_revid
2346
 
                    for (parent_revid,) in parent_keys)
 
2675
                result[revision_id] = tuple([parent_revid
 
2676
                    for (parent_revid,) in parent_keys])
2347
2677
            else:
2348
2678
                result[revision_id] = (_mod_revision.NULL_REVISION,)
2349
2679
        return result
2356
2686
        parents_provider = self._make_parents_provider()
2357
2687
        if (other_repository is not None and
2358
2688
            not self.has_same_location(other_repository)):
2359
 
            parents_provider = graph._StackedParentsProvider(
 
2689
            parents_provider = graph.StackedParentsProvider(
2360
2690
                [parents_provider, other_repository._make_parents_provider()])
2361
2691
        return graph.Graph(parents_provider)
2362
2692
 
2363
 
    def _get_versioned_file_checker(self, text_key_references=None):
 
2693
    def _get_versioned_file_checker(self, text_key_references=None,
 
2694
        ancestors=None):
2364
2695
        """Return an object suitable for checking versioned files.
2365
2696
        
2366
2697
        :param text_key_references: if non-None, an already built
2368
2699
            to whether they were referred to by the inventory of the
2369
2700
            revision_id that they contain. If None, this will be
2370
2701
            calculated.
 
2702
        :param ancestors: Optional result from
 
2703
            self.get_graph().get_parent_map(self.all_revision_ids()) if already
 
2704
            available.
2371
2705
        """
2372
2706
        return _VersionedFileChecker(self,
2373
 
            text_key_references=text_key_references)
 
2707
            text_key_references=text_key_references, ancestors=ancestors)
2374
2708
 
2375
2709
    def revision_ids_to_search_result(self, result_set):
2376
2710
        """Convert a set of revision ids to a graph SearchResult."""
2426
2760
        return record.get_bytes_as('fulltext')
2427
2761
 
2428
2762
    @needs_read_lock
2429
 
    def check(self, revision_ids=None):
 
2763
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2430
2764
        """Check consistency of all history of given revision_ids.
2431
2765
 
2432
2766
        Different repository implementations should override _check().
2433
2767
 
2434
2768
        :param revision_ids: A non-empty list of revision_ids whose ancestry
2435
2769
             will be checked.  Typically the last revision_id of a branch.
 
2770
        :param callback_refs: A dict of check-refs to resolve and callback
 
2771
            the check/_check method on the items listed as wanting the ref.
 
2772
            see bzrlib.check.
 
2773
        :param check_repo: If False do not check the repository contents, just 
 
2774
            calculate the data callback_refs requires and call them back.
2436
2775
        """
2437
 
        return self._check(revision_ids)
 
2776
        return self._check(revision_ids, callback_refs=callback_refs,
 
2777
            check_repo=check_repo)
2438
2778
 
2439
 
    def _check(self, revision_ids):
2440
 
        result = check.Check(self)
2441
 
        result.check()
 
2779
    def _check(self, revision_ids, callback_refs, check_repo):
 
2780
        result = check.Check(self, check_repo=check_repo)
 
2781
        result.check(callback_refs)
2442
2782
        return result
2443
2783
 
2444
 
    def _warn_if_deprecated(self):
 
2784
    def _warn_if_deprecated(self, branch=None):
2445
2785
        global _deprecation_warning_done
2446
2786
        if _deprecation_warning_done:
2447
2787
            return
2448
 
        _deprecation_warning_done = True
2449
 
        warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
2450
 
                % (self._format, self.bzrdir.transport.base))
 
2788
        try:
 
2789
            if branch is None:
 
2790
                conf = config.GlobalConfig()
 
2791
            else:
 
2792
                conf = branch.get_config()
 
2793
            if conf.suppress_warning('format_deprecation'):
 
2794
                return
 
2795
            warning("Format %s for %s is deprecated -"
 
2796
                    " please use 'bzr upgrade' to get better performance"
 
2797
                    % (self._format, self.bzrdir.transport.base))
 
2798
        finally:
 
2799
            _deprecation_warning_done = True
2451
2800
 
2452
2801
    def supports_rich_root(self):
2453
2802
        return self._format.rich_root_data
2729
3078
    # Does this format have < O(tree_size) delta generation. Used to hint what
2730
3079
    # code path for commit, amongst other things.
2731
3080
    fast_deltas = None
 
3081
    # Does doing a pack operation compress data? Useful for the pack UI command
 
3082
    # (so if there is one pack, the operation can still proceed because it may
 
3083
    # help), and for fetching when data won't have come from the same
 
3084
    # compressor.
 
3085
    pack_compresses = False
 
3086
    # Does the repository inventory storage understand references to trees?
 
3087
    supports_tree_reference = None
2732
3088
 
2733
3089
    def __str__(self):
2734
3090
        return "<%s>" % self.__class__.__name__
2750
3106
        """
2751
3107
        try:
2752
3108
            transport = a_bzrdir.get_repository_transport(None)
2753
 
            format_string = transport.get("format").read()
 
3109
            format_string = transport.get_bytes("format")
2754
3110
            return format_registry.get(format_string)
2755
3111
        except errors.NoSuchFile:
2756
3112
            raise errors.NoRepositoryPresent(a_bzrdir)
2838
3194
        raise NotImplementedError(self.network_name)
2839
3195
 
2840
3196
    def check_conversion_target(self, target_format):
2841
 
        raise NotImplementedError(self.check_conversion_target)
 
3197
        if self.rich_root_data and not target_format.rich_root_data:
 
3198
            raise errors.BadConversionTarget(
 
3199
                'Does not support rich root data.', target_format,
 
3200
                from_format=self)
 
3201
        if (self.supports_tree_reference and 
 
3202
            not getattr(target_format, 'supports_tree_reference', False)):
 
3203
            raise errors.BadConversionTarget(
 
3204
                'Does not support nested trees', target_format,
 
3205
                from_format=self)
2842
3206
 
2843
3207
    def open(self, a_bzrdir, _found=False):
2844
3208
        """Return an instance of this format for the bzrdir a_bzrdir.
3003
3367
    'RepositoryFormatCHK1',
3004
3368
    )
3005
3369
 
 
3370
format_registry.register_lazy(
 
3371
    'Bazaar development format - chk repository with bencode revision '
 
3372
        'serialization (needs bzr.dev from 1.16)\n',
 
3373
    'bzrlib.repofmt.groupcompress_repo',
 
3374
    'RepositoryFormatCHK2',
 
3375
    )
 
3376
format_registry.register_lazy(
 
3377
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
3378
    'bzrlib.repofmt.groupcompress_repo',
 
3379
    'RepositoryFormat2a',
 
3380
    )
 
3381
 
3006
3382
 
3007
3383
class InterRepository(InterObject):
3008
3384
    """This class represents operations taking place between two repositories.
3049
3425
                   provided a default one will be created.
3050
3426
        :return: None.
3051
3427
        """
3052
 
        from bzrlib.fetch import RepoFetcher
3053
 
        f = RepoFetcher(to_repository=self.target,
 
3428
        f = _mod_fetch.RepoFetcher(to_repository=self.target,
3054
3429
                               from_repository=self.source,
3055
3430
                               last_revision=revision_id,
3056
3431
                               fetch_spec=fetch_spec,
3064
3439
        """
3065
3440
        target_graph = self.target.get_graph()
3066
3441
        revision_ids = frozenset(revision_ids)
3067
 
        # Fast path for the case where all the revisions are already in the
3068
 
        # target repo.
3069
 
        # (Although this does incur an extra round trip for the
3070
 
        # fairly common case where the target doesn't already have the revision
3071
 
        # we're pushing.)
3072
 
        if set(target_graph.get_parent_map(revision_ids)) == revision_ids:
3073
 
            return graph.SearchResult(revision_ids, set(), 0, set())
3074
3442
        missing_revs = set()
3075
3443
        source_graph = self.source.get_graph()
3076
3444
        # ensure we don't pay silly lookup costs.
3236
3604
                self.target.texts.insert_record_stream(
3237
3605
                    self.source.texts.get_record_stream(
3238
3606
                        self.source.texts.keys(), 'topological', False))
3239
 
                pb.update('copying inventory', 0, 1)
 
3607
                pb.update('Copying inventory', 0, 1)
3240
3608
                self.target.inventories.insert_record_stream(
3241
3609
                    self.source.inventories.get_record_stream(
3242
3610
                        self.source.inventories.keys(), 'topological', False))
3356
3724
        return self.source.revision_ids_to_search_result(result_set)
3357
3725
 
3358
3726
 
3359
 
class InterPackRepo(InterSameDataRepository):
3360
 
    """Optimised code paths between Pack based repositories."""
3361
 
 
3362
 
    @classmethod
3363
 
    def _get_repo_format_to_test(self):
3364
 
        from bzrlib.repofmt import pack_repo
3365
 
        return pack_repo.RepositoryFormatKnitPack6RichRoot()
3366
 
 
3367
 
    @staticmethod
3368
 
    def is_compatible(source, target):
3369
 
        """Be compatible with known Pack formats.
3370
 
 
3371
 
        We don't test for the stores being of specific types because that
3372
 
        could lead to confusing results, and there is no need to be
3373
 
        overly general.
3374
 
 
3375
 
        InterPackRepo does not support CHK based repositories.
3376
 
        """
3377
 
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
3378
 
        from bzrlib.repofmt.groupcompress_repo import RepositoryFormatCHK1
3379
 
        try:
3380
 
            are_packs = (isinstance(source._format, RepositoryFormatPack) and
3381
 
                isinstance(target._format, RepositoryFormatPack))
3382
 
            not_packs = (isinstance(source._format, RepositoryFormatCHK1) or
3383
 
                isinstance(target._format, RepositoryFormatCHK1))
3384
 
        except AttributeError:
3385
 
            return False
3386
 
        if not_packs or not are_packs:
3387
 
            return False
3388
 
        return InterRepository._same_model(source, target)
3389
 
 
3390
 
    @needs_write_lock
3391
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3392
 
            fetch_spec=None):
3393
 
        """See InterRepository.fetch()."""
3394
 
        if (len(self.source._fallback_repositories) > 0 or
3395
 
            len(self.target._fallback_repositories) > 0):
3396
 
            # The pack layer is not aware of fallback repositories, so when
3397
 
            # fetching from a stacked repository or into a stacked repository
3398
 
            # we use the generic fetch logic which uses the VersionedFiles
3399
 
            # attributes on repository.
3400
 
            from bzrlib.fetch import RepoFetcher
3401
 
            fetcher = RepoFetcher(self.target, self.source, revision_id,
3402
 
                    pb, find_ghosts, fetch_spec=fetch_spec)
3403
 
        if fetch_spec is not None:
3404
 
            if len(list(fetch_spec.heads)) != 1:
3405
 
                raise AssertionError(
3406
 
                    "InterPackRepo.fetch doesn't support "
3407
 
                    "fetching multiple heads yet.")
3408
 
            revision_id = list(fetch_spec.heads)[0]
3409
 
            fetch_spec = None
3410
 
        if revision_id is None:
3411
 
            # TODO:
3412
 
            # everything to do - use pack logic
3413
 
            # to fetch from all packs to one without
3414
 
            # inventory parsing etc, IFF nothing to be copied is in the target.
3415
 
            # till then:
3416
 
            source_revision_ids = frozenset(self.source.all_revision_ids())
3417
 
            revision_ids = source_revision_ids - \
3418
 
                frozenset(self.target.get_parent_map(source_revision_ids))
3419
 
            revision_keys = [(revid,) for revid in revision_ids]
3420
 
            index = self.target._pack_collection.revision_index.combined_index
3421
 
            present_revision_ids = set(item[1][0] for item in
3422
 
                index.iter_entries(revision_keys))
3423
 
            revision_ids = set(revision_ids) - present_revision_ids
3424
 
            # implementing the TODO will involve:
3425
 
            # - detecting when all of a pack is selected
3426
 
            # - avoiding as much as possible pre-selection, so the
3427
 
            # more-core routines such as create_pack_from_packs can filter in
3428
 
            # a just-in-time fashion. (though having a HEADS list on a
3429
 
            # repository might make this a lot easier, because we could
3430
 
            # sensibly detect 'new revisions' without doing a full index scan.
3431
 
        elif _mod_revision.is_null(revision_id):
3432
 
            # nothing to do:
3433
 
            return (0, [])
3434
 
        else:
3435
 
            try:
3436
 
                revision_ids = self.search_missing_revision_ids(revision_id,
3437
 
                    find_ghosts=find_ghosts).get_keys()
3438
 
            except errors.NoSuchRevision:
3439
 
                raise errors.InstallFailed([revision_id])
3440
 
            if len(revision_ids) == 0:
3441
 
                return (0, [])
3442
 
        return self._pack(self.source, self.target, revision_ids)
3443
 
 
3444
 
    def _pack(self, source, target, revision_ids):
3445
 
        from bzrlib.repofmt.pack_repo import Packer
3446
 
        packs = source._pack_collection.all_packs()
3447
 
        pack = Packer(self.target._pack_collection, packs, '.fetch',
3448
 
            revision_ids).pack()
3449
 
        if pack is not None:
3450
 
            self.target._pack_collection._save_pack_names()
3451
 
            copied_revs = pack.get_revision_count()
3452
 
            # Trigger an autopack. This may duplicate effort as we've just done
3453
 
            # a pack creation, but for now it is simpler to think about as
3454
 
            # 'upload data, then repack if needed'.
3455
 
            self.target._pack_collection.autopack()
3456
 
            return (copied_revs, [])
3457
 
        else:
3458
 
            return (0, [])
3459
 
 
3460
 
    @needs_read_lock
3461
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3462
 
        """See InterRepository.missing_revision_ids().
3463
 
 
3464
 
        :param find_ghosts: Find ghosts throughout the ancestry of
3465
 
            revision_id.
3466
 
        """
3467
 
        if not find_ghosts and revision_id is not None:
3468
 
            return self._walk_to_common_revisions([revision_id])
3469
 
        elif revision_id is not None:
3470
 
            # Find ghosts: search for revisions pointing from one repository to
3471
 
            # the other, and vice versa, anywhere in the history of revision_id.
3472
 
            graph = self.target.get_graph(other_repository=self.source)
3473
 
            searcher = graph._make_breadth_first_searcher([revision_id])
3474
 
            found_ids = set()
3475
 
            while True:
3476
 
                try:
3477
 
                    next_revs, ghosts = searcher.next_with_ghosts()
3478
 
                except StopIteration:
3479
 
                    break
3480
 
                if revision_id in ghosts:
3481
 
                    raise errors.NoSuchRevision(self.source, revision_id)
3482
 
                found_ids.update(next_revs)
3483
 
                found_ids.update(ghosts)
3484
 
            found_ids = frozenset(found_ids)
3485
 
            # Double query here: should be able to avoid this by changing the
3486
 
            # graph api further.
3487
 
            result_set = found_ids - frozenset(
3488
 
                self.target.get_parent_map(found_ids))
3489
 
        else:
3490
 
            source_ids = self.source.all_revision_ids()
3491
 
            # source_ids is the worst possible case we may need to pull.
3492
 
            # now we want to filter source_ids against what we actually
3493
 
            # have in target, but don't try to check for existence where we know
3494
 
            # we do not have a revision as that would be pointless.
3495
 
            target_ids = set(self.target.all_revision_ids())
3496
 
            result_set = set(source_ids).difference(target_ids)
3497
 
        return self.source.revision_ids_to_search_result(result_set)
3498
 
 
3499
 
 
3500
3727
class InterDifferingSerializer(InterRepository):
3501
3728
 
3502
3729
    @classmethod
3509
3736
        # This is redundant with format.check_conversion_target(), however that
3510
3737
        # raises an exception, and we just want to say "False" as in we won't
3511
3738
        # support converting between these formats.
 
3739
        if 'IDS_never' in debug.debug_flags:
 
3740
            return False
3512
3741
        if source.supports_rich_root() and not target.supports_rich_root():
3513
3742
            return False
3514
3743
        if (source._format.supports_tree_reference
3515
3744
            and not target._format.supports_tree_reference):
3516
3745
            return False
 
3746
        if target._fallback_repositories and target._format.supports_chks:
 
3747
            # IDS doesn't know how to copy CHKs for the parent inventories it
 
3748
            # adds to stacked repos.
 
3749
            return False
 
3750
        if 'IDS_always' in debug.debug_flags:
 
3751
            return True
 
3752
        # Only use this code path for local source and target.  IDS does far
 
3753
        # too much IO (both bandwidth and roundtrips) over a network.
 
3754
        if not source.bzrdir.transport.base.startswith('file:///'):
 
3755
            return False
 
3756
        if not target.bzrdir.transport.base.startswith('file:///'):
 
3757
            return False
3517
3758
        return True
3518
3759
 
3519
 
    def _get_delta_for_revision(self, tree, parent_ids, basis_id, cache):
 
3760
    def _get_trees(self, revision_ids, cache):
 
3761
        possible_trees = []
 
3762
        for rev_id in revision_ids:
 
3763
            if rev_id in cache:
 
3764
                possible_trees.append((rev_id, cache[rev_id]))
 
3765
            else:
 
3766
                # Not cached, but inventory might be present anyway.
 
3767
                try:
 
3768
                    tree = self.source.revision_tree(rev_id)
 
3769
                except errors.NoSuchRevision:
 
3770
                    # Nope, parent is ghost.
 
3771
                    pass
 
3772
                else:
 
3773
                    cache[rev_id] = tree
 
3774
                    possible_trees.append((rev_id, tree))
 
3775
        return possible_trees
 
3776
 
 
3777
    def _get_delta_for_revision(self, tree, parent_ids, possible_trees):
3520
3778
        """Get the best delta and base for this revision.
3521
3779
 
3522
3780
        :return: (basis_id, delta)
3523
3781
        """
3524
 
        possible_trees = [(parent_id, cache[parent_id])
3525
 
                          for parent_id in parent_ids
3526
 
                           if parent_id in cache]
3527
 
        if len(possible_trees) == 0:
3528
 
            # There either aren't any parents, or the parents aren't in the
3529
 
            # cache, so just use the last converted tree
3530
 
            possible_trees.append((basis_id, cache[basis_id]))
3531
3782
        deltas = []
 
3783
        # Generate deltas against each tree, to find the shortest.
 
3784
        texts_possibly_new_in_tree = set()
3532
3785
        for basis_id, basis_tree in possible_trees:
3533
3786
            delta = tree.inventory._make_delta(basis_tree.inventory)
 
3787
            for old_path, new_path, file_id, new_entry in delta:
 
3788
                if new_path is None:
 
3789
                    # This file_id isn't present in the new rev, so we don't
 
3790
                    # care about it.
 
3791
                    continue
 
3792
                if not new_path:
 
3793
                    # Rich roots are handled elsewhere...
 
3794
                    continue
 
3795
                kind = new_entry.kind
 
3796
                if kind != 'directory' and kind != 'file':
 
3797
                    # No text record associated with this inventory entry.
 
3798
                    continue
 
3799
                # This is a directory or file that has changed somehow.
 
3800
                texts_possibly_new_in_tree.add((file_id, new_entry.revision))
3534
3801
            deltas.append((len(delta), basis_id, delta))
3535
3802
        deltas.sort()
3536
3803
        return deltas[0][1:]
3537
3804
 
3538
 
    def _get_parent_keys(self, root_key, parent_map):
3539
 
        """Get the parent keys for a given root id."""
3540
 
        root_id, rev_id = root_key
3541
 
        # Include direct parents of the revision, but only if they used
3542
 
        # the same root_id.
3543
 
        parent_keys = []
3544
 
        for parent_id in parent_map[rev_id]:
3545
 
            if parent_id == _mod_revision.NULL_REVISION:
3546
 
                continue
3547
 
            if parent_id not in self._revision_id_to_root_id:
3548
 
                # We probably didn't read this revision, go spend the
3549
 
                # extra effort to actually check
3550
 
                try:
3551
 
                    tree = self.source.revision_tree(parent_id)
3552
 
                except errors.NoSuchRevision:
3553
 
                    # Ghost, fill out _revision_id_to_root_id in case we
3554
 
                    # encounter this again.
3555
 
                    # But set parent_root_id to None since we don't really know
3556
 
                    parent_root_id = None
3557
 
                else:
3558
 
                    parent_root_id = tree.get_root_id()
3559
 
                self._revision_id_to_root_id[parent_id] = None
3560
 
            else:
3561
 
                parent_root_id = self._revision_id_to_root_id[parent_id]
3562
 
            if root_id == parent_root_id or parent_root_id is None:
3563
 
                parent_keys.append((root_id, parent_id))
3564
 
        return tuple(parent_keys)
3565
 
 
3566
 
    def _new_root_data_stream(self, root_keys_to_create, parent_map):
3567
 
        for root_key in root_keys_to_create:
3568
 
            parent_keys = self._get_parent_keys(root_key, parent_map)
3569
 
            yield versionedfile.FulltextContentFactory(root_key,
3570
 
                parent_keys, None, '')
3571
 
 
3572
 
    def _fetch_batch(self, revision_ids, basis_id, cache):
 
3805
    def _fetch_parent_invs_for_stacking(self, parent_map, cache):
 
3806
        """Find all parent revisions that are absent, but for which the
 
3807
        inventory is present, and copy those inventories.
 
3808
 
 
3809
        This is necessary to preserve correctness when the source is stacked
 
3810
        without fallbacks configured.  (Note that in cases like upgrade the
 
3811
        source may be not have _fallback_repositories even though it is
 
3812
        stacked.)
 
3813
        """
 
3814
        parent_revs = set()
 
3815
        for parents in parent_map.values():
 
3816
            parent_revs.update(parents)
 
3817
        present_parents = self.source.get_parent_map(parent_revs)
 
3818
        absent_parents = set(parent_revs).difference(present_parents)
 
3819
        parent_invs_keys_for_stacking = self.source.inventories.get_parent_map(
 
3820
            (rev_id,) for rev_id in absent_parents)
 
3821
        parent_inv_ids = [key[-1] for key in parent_invs_keys_for_stacking]
 
3822
        for parent_tree in self.source.revision_trees(parent_inv_ids):
 
3823
            current_revision_id = parent_tree.get_revision_id()
 
3824
            parents_parents_keys = parent_invs_keys_for_stacking[
 
3825
                (current_revision_id,)]
 
3826
            parents_parents = [key[-1] for key in parents_parents_keys]
 
3827
            basis_id = _mod_revision.NULL_REVISION
 
3828
            basis_tree = self.source.revision_tree(basis_id)
 
3829
            delta = parent_tree.inventory._make_delta(basis_tree.inventory)
 
3830
            self.target.add_inventory_by_delta(
 
3831
                basis_id, delta, current_revision_id, parents_parents)
 
3832
            cache[current_revision_id] = parent_tree
 
3833
 
 
3834
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
3573
3835
        """Fetch across a few revisions.
3574
3836
 
3575
3837
        :param revision_ids: The revisions to copy
3576
3838
        :param basis_id: The revision_id of a tree that must be in cache, used
3577
3839
            as a basis for delta when no other base is available
3578
3840
        :param cache: A cache of RevisionTrees that we can use.
 
3841
        :param a_graph: A Graph object to determine the heads() of the
 
3842
            rich-root data stream.
3579
3843
        :return: The revision_id of the last converted tree. The RevisionTree
3580
3844
            for it will be in cache
3581
3845
        """
3587
3851
        pending_deltas = []
3588
3852
        pending_revisions = []
3589
3853
        parent_map = self.source.get_parent_map(revision_ids)
 
3854
        self._fetch_parent_invs_for_stacking(parent_map, cache)
 
3855
        self.source._safe_to_return_from_cache = True
3590
3856
        for tree in self.source.revision_trees(revision_ids):
 
3857
            # Find a inventory delta for this revision.
 
3858
            # Find text entries that need to be copied, too.
3591
3859
            current_revision_id = tree.get_revision_id()
3592
3860
            parent_ids = parent_map.get(current_revision_id, ())
 
3861
            parent_trees = self._get_trees(parent_ids, cache)
 
3862
            possible_trees = list(parent_trees)
 
3863
            if len(possible_trees) == 0:
 
3864
                # There either aren't any parents, or the parents are ghosts,
 
3865
                # so just use the last converted tree.
 
3866
                possible_trees.append((basis_id, cache[basis_id]))
3593
3867
            basis_id, delta = self._get_delta_for_revision(tree, parent_ids,
3594
 
                                                           basis_id, cache)
 
3868
                                                           possible_trees)
 
3869
            revision = self.source.get_revision(current_revision_id)
 
3870
            pending_deltas.append((basis_id, delta,
 
3871
                current_revision_id, revision.parent_ids))
3595
3872
            if self._converting_to_rich_root:
3596
3873
                self._revision_id_to_root_id[current_revision_id] = \
3597
3874
                    tree.get_root_id()
3598
 
            # Find text entries that need to be copied
 
3875
            # Determine which texts are in present in this revision but not in
 
3876
            # any of the available parents.
 
3877
            texts_possibly_new_in_tree = set()
3599
3878
            for old_path, new_path, file_id, entry in delta:
3600
 
                if new_path is not None:
3601
 
                    if not new_path:
3602
 
                        # This is the root
3603
 
                        if not self.target.supports_rich_root():
3604
 
                            # The target doesn't support rich root, so we don't
3605
 
                            # copy
3606
 
                            continue
3607
 
                        if self._converting_to_rich_root:
3608
 
                            # This can't be copied normally, we have to insert
3609
 
                            # it specially
3610
 
                            root_keys_to_create.add((file_id, entry.revision))
3611
 
                            continue
3612
 
                    text_keys.add((file_id, entry.revision))
3613
 
            revision = self.source.get_revision(current_revision_id)
3614
 
            pending_deltas.append((basis_id, delta,
3615
 
                current_revision_id, revision.parent_ids))
 
3879
                if new_path is None:
 
3880
                    # This file_id isn't present in the new rev
 
3881
                    continue
 
3882
                if not new_path:
 
3883
                    # This is the root
 
3884
                    if not self.target.supports_rich_root():
 
3885
                        # The target doesn't support rich root, so we don't
 
3886
                        # copy
 
3887
                        continue
 
3888
                    if self._converting_to_rich_root:
 
3889
                        # This can't be copied normally, we have to insert
 
3890
                        # it specially
 
3891
                        root_keys_to_create.add((file_id, entry.revision))
 
3892
                        continue
 
3893
                kind = entry.kind
 
3894
                texts_possibly_new_in_tree.add((file_id, entry.revision))
 
3895
            for basis_id, basis_tree in possible_trees:
 
3896
                basis_inv = basis_tree.inventory
 
3897
                for file_key in list(texts_possibly_new_in_tree):
 
3898
                    file_id, file_revision = file_key
 
3899
                    try:
 
3900
                        entry = basis_inv[file_id]
 
3901
                    except errors.NoSuchId:
 
3902
                        continue
 
3903
                    if entry.revision == file_revision:
 
3904
                        texts_possibly_new_in_tree.remove(file_key)
 
3905
            text_keys.update(texts_possibly_new_in_tree)
3616
3906
            pending_revisions.append(revision)
3617
3907
            cache[current_revision_id] = tree
3618
3908
            basis_id = current_revision_id
 
3909
        self.source._safe_to_return_from_cache = False
3619
3910
        # Copy file texts
3620
3911
        from_texts = self.source.texts
3621
3912
        to_texts = self.target.texts
3622
3913
        if root_keys_to_create:
3623
 
            root_stream = self._new_root_data_stream(root_keys_to_create,
3624
 
                                                     parent_map)
 
3914
            root_stream = _mod_fetch._new_root_data_stream(
 
3915
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
 
3916
                self.source, graph=a_graph)
3625
3917
            to_texts.insert_record_stream(root_stream)
3626
3918
        to_texts.insert_record_stream(from_texts.get_record_stream(
3627
3919
            text_keys, self.target._format._fetch_order,
3634
3926
            # for the new revisions that we are about to insert.  We do this
3635
3927
            # before adding the revisions so that no revision is added until
3636
3928
            # all the inventories it may depend on are added.
 
3929
            # Note that this is overzealous, as we may have fetched these in an
 
3930
            # earlier batch.
3637
3931
            parent_ids = set()
3638
3932
            revision_ids = set()
3639
3933
            for revision in pending_revisions:
3642
3936
            parent_ids.difference_update(revision_ids)
3643
3937
            parent_ids.discard(_mod_revision.NULL_REVISION)
3644
3938
            parent_map = self.source.get_parent_map(parent_ids)
3645
 
            for parent_tree in self.source.revision_trees(parent_ids):
3646
 
                basis_id, delta = self._get_delta_for_revision(tree, parent_ids, basis_id, cache)
 
3939
            # we iterate over parent_map and not parent_ids because we don't
 
3940
            # want to try copying any revision which is a ghost
 
3941
            for parent_tree in self.source.revision_trees(parent_map):
3647
3942
                current_revision_id = parent_tree.get_revision_id()
3648
3943
                parents_parents = parent_map[current_revision_id]
 
3944
                possible_trees = self._get_trees(parents_parents, cache)
 
3945
                if len(possible_trees) == 0:
 
3946
                    # There either aren't any parents, or the parents are
 
3947
                    # ghosts, so just use the last converted tree.
 
3948
                    possible_trees.append((basis_id, cache[basis_id]))
 
3949
                basis_id, delta = self._get_delta_for_revision(parent_tree,
 
3950
                    parents_parents, possible_trees)
3649
3951
                self.target.add_inventory_by_delta(
3650
3952
                    basis_id, delta, current_revision_id, parents_parents)
3651
3953
        # insert signatures and revisions
3665
3967
 
3666
3968
        :param revision_ids: The list of revisions to fetch. Must be in
3667
3969
            topological order.
3668
 
        :param pb: A ProgressBar
 
3970
        :param pb: A ProgressTask
3669
3971
        :return: None
3670
3972
        """
3671
3973
        basis_id, basis_tree = self._get_basis(revision_ids[0])
3673
3975
        cache = lru_cache.LRUCache(100)
3674
3976
        cache[basis_id] = basis_tree
3675
3977
        del basis_tree # We don't want to hang on to it here
 
3978
        hints = []
 
3979
        if self._converting_to_rich_root and len(revision_ids) > 100:
 
3980
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
 
3981
                                                            revision_ids)
 
3982
        else:
 
3983
            a_graph = None
 
3984
 
3676
3985
        for offset in range(0, len(revision_ids), batch_size):
3677
3986
            self.target.start_write_group()
3678
3987
            try:
3679
3988
                pb.update('Transferring revisions', offset,
3680
3989
                          len(revision_ids))
3681
3990
                batch = revision_ids[offset:offset+batch_size]
3682
 
                basis_id = self._fetch_batch(batch, basis_id, cache)
 
3991
                basis_id = self._fetch_batch(batch, basis_id, cache,
 
3992
                                             a_graph=a_graph)
3683
3993
            except:
 
3994
                self.source._safe_to_return_from_cache = False
3684
3995
                self.target.abort_write_group()
3685
3996
                raise
3686
3997
            else:
3687
 
                self.target.commit_write_group()
 
3998
                hint = self.target.commit_write_group()
 
3999
                if hint:
 
4000
                    hints.extend(hint)
 
4001
        if hints and self.target._format.pack_compresses:
 
4002
            self.target.pack(hint=hints)
3688
4003
        pb.update('Transferring revisions', len(revision_ids),
3689
4004
                  len(revision_ids))
3690
4005
 
3694
4009
        """See InterRepository.fetch()."""
3695
4010
        if fetch_spec is not None:
3696
4011
            raise AssertionError("Not implemented yet...")
 
4012
        # See <https://launchpad.net/bugs/456077> asking for a warning here
 
4013
        #
 
4014
        # nb this is only active for local-local fetches; other things using
 
4015
        # streaming.
 
4016
        trace.warning("Fetching between repositories with different formats\n"
 
4017
            "from %s to %s.\n"
 
4018
            "This may take some time. Upgrade the branches to the same format \n"
 
4019
            "for better results.\n"
 
4020
            % (self.source._format, self.target._format))
3697
4021
        if (not self.source.supports_rich_root()
3698
4022
            and self.target.supports_rich_root()):
3699
4023
            self._converting_to_rich_root = True
3711
4035
        # Walk though all revisions; get inventory deltas, copy referenced
3712
4036
        # texts that delta references, insert the delta, revision and
3713
4037
        # signature.
3714
 
        first_rev = self.source.get_revision(revision_ids[0])
3715
4038
        if pb is None:
3716
4039
            my_pb = ui.ui_factory.nested_progress_bar()
3717
4040
            pb = my_pb
3754
4077
InterRepository.register_optimiser(InterSameDataRepository)
3755
4078
InterRepository.register_optimiser(InterWeaveRepo)
3756
4079
InterRepository.register_optimiser(InterKnitRepo)
3757
 
InterRepository.register_optimiser(InterPackRepo)
3758
4080
 
3759
4081
 
3760
4082
class CopyConverter(object):
3795
4117
                                                  self.source_repo.is_shared())
3796
4118
        converted.lock_write()
3797
4119
        try:
3798
 
            self.step('Copying content into repository.')
 
4120
            self.step('Copying content')
3799
4121
            self.source_repo.copy_content_into(converted)
3800
4122
        finally:
3801
4123
            converted.unlock()
3802
 
        self.step('Deleting old repository content.')
 
4124
        self.step('Deleting old repository content')
3803
4125
        self.repo_dir.transport.delete_tree('repository.backup')
3804
 
        self.pb.note('repository converted')
 
4126
        ui.ui_factory.note('repository converted')
3805
4127
 
3806
4128
    def step(self, message):
3807
4129
        """Update the pb by a step."""
3841
4163
 
3842
4164
class _VersionedFileChecker(object):
3843
4165
 
3844
 
    def __init__(self, repository, text_key_references=None):
 
4166
    def __init__(self, repository, text_key_references=None, ancestors=None):
3845
4167
        self.repository = repository
3846
4168
        self.text_index = self.repository._generate_text_key_index(
3847
 
            text_key_references=text_key_references)
 
4169
            text_key_references=text_key_references, ancestors=ancestors)
3848
4170
 
3849
4171
    def calculate_file_version_parents(self, text_key):
3850
4172
        """Calculate the correct parents for a file version according to
3868
4190
            revision_id) tuples for versions that are present in this versioned
3869
4191
            file, but not used by the corresponding inventory.
3870
4192
        """
 
4193
        local_progress = None
 
4194
        if progress_bar is None:
 
4195
            local_progress = ui.ui_factory.nested_progress_bar()
 
4196
            progress_bar = local_progress
 
4197
        try:
 
4198
            return self._check_file_version_parents(texts, progress_bar)
 
4199
        finally:
 
4200
            if local_progress:
 
4201
                local_progress.finished()
 
4202
 
 
4203
    def _check_file_version_parents(self, texts, progress_bar):
 
4204
        """See check_file_version_parents."""
3871
4205
        wrong_parents = {}
3872
4206
        self.file_ids = set([file_id for file_id, _ in
3873
4207
            self.text_index.iterkeys()])
3874
4208
        # text keys is now grouped by file_id
3875
 
        n_weaves = len(self.file_ids)
3876
 
        files_in_revisions = {}
3877
 
        revisions_of_files = {}
3878
4209
        n_versions = len(self.text_index)
3879
4210
        progress_bar.update('loading text store', 0, n_versions)
3880
4211
        parent_map = self.repository.texts.get_parent_map(self.text_index)
3882
4213
        text_keys = self.repository.texts.keys()
3883
4214
        unused_keys = frozenset(text_keys) - set(self.text_index)
3884
4215
        for num, key in enumerate(self.text_index.iterkeys()):
3885
 
            if progress_bar is not None:
3886
 
                progress_bar.update('checking text graph', num, n_versions)
 
4216
            progress_bar.update('checking text graph', num, n_versions)
3887
4217
            correct_parents = self.calculate_file_version_parents(key)
3888
4218
            try:
3889
4219
                knit_parents = parent_map[key]
3938
4268
        try:
3939
4269
            if resume_tokens:
3940
4270
                self.target_repo.resume_write_group(resume_tokens)
 
4271
                is_resume = True
3941
4272
            else:
3942
4273
                self.target_repo.start_write_group()
 
4274
                is_resume = False
3943
4275
            try:
3944
4276
                # locked_insert_stream performs a commit|suspend.
3945
 
                return self._locked_insert_stream(stream, src_format)
 
4277
                return self._locked_insert_stream(stream, src_format, is_resume)
3946
4278
            except:
3947
4279
                self.target_repo.abort_write_group(suppress_errors=True)
3948
4280
                raise
3949
4281
        finally:
3950
4282
            self.target_repo.unlock()
3951
4283
 
3952
 
    def _locked_insert_stream(self, stream, src_format):
 
4284
    def _locked_insert_stream(self, stream, src_format, is_resume):
3953
4285
        to_serializer = self.target_repo._format._serializer
3954
4286
        src_serializer = src_format._serializer
 
4287
        new_pack = None
3955
4288
        if to_serializer == src_serializer:
3956
4289
            # If serializers match and the target is a pack repository, set the
3957
4290
            # write cache size on the new pack.  This avoids poor performance
3971
4304
            else:
3972
4305
                new_pack.set_write_cache_size(1024*1024)
3973
4306
        for substream_type, substream in stream:
 
4307
            if 'stream' in debug.debug_flags:
 
4308
                mutter('inserting substream: %s', substream_type)
3974
4309
            if substream_type == 'texts':
3975
4310
                self.target_repo.texts.insert_record_stream(substream)
3976
4311
            elif substream_type == 'inventories':
3980
4315
                else:
3981
4316
                    self._extract_and_insert_inventories(
3982
4317
                        substream, src_serializer)
 
4318
            elif substream_type == 'inventory-deltas':
 
4319
                self._extract_and_insert_inventory_deltas(
 
4320
                    substream, src_serializer)
3983
4321
            elif substream_type == 'chk_bytes':
3984
4322
                # XXX: This doesn't support conversions, as it assumes the
3985
4323
                #      conversion was done in the fetch code.
3998
4336
                self.target_repo.signatures.insert_record_stream(substream)
3999
4337
            else:
4000
4338
                raise AssertionError('kaboom! %s' % (substream_type,))
 
4339
        # Done inserting data, and the missing_keys calculations will try to
 
4340
        # read back from the inserted data, so flush the writes to the new pack
 
4341
        # (if this is pack format).
 
4342
        if new_pack is not None:
 
4343
            new_pack._write_data('', flush=True)
 
4344
        # Find all the new revisions (including ones from resume_tokens)
 
4345
        missing_keys = self.target_repo.get_missing_parent_inventories(
 
4346
            check_for_missing_texts=is_resume)
4001
4347
        try:
4002
 
            missing_keys = set()
4003
4348
            for prefix, versioned_file in (
4004
4349
                ('texts', self.target_repo.texts),
4005
4350
                ('inventories', self.target_repo.inventories),
4006
4351
                ('revisions', self.target_repo.revisions),
4007
4352
                ('signatures', self.target_repo.signatures),
 
4353
                ('chk_bytes', self.target_repo.chk_bytes),
4008
4354
                ):
 
4355
                if versioned_file is None:
 
4356
                    continue
 
4357
                # TODO: key is often going to be a StaticTuple object
 
4358
                #       I don't believe we can define a method by which
 
4359
                #       (prefix,) + StaticTuple will work, though we could
 
4360
                #       define a StaticTuple.sq_concat that would allow you to
 
4361
                #       pass in either a tuple or a StaticTuple as the second
 
4362
                #       object, so instead we could have:
 
4363
                #       StaticTuple(prefix) + key here...
4009
4364
                missing_keys.update((prefix,) + key for key in
4010
4365
                    versioned_file.get_missing_compression_parent_keys())
4011
4366
        except NotImplementedError:
4020
4375
                # missing keys can handle suspending a write group).
4021
4376
                write_group_tokens = self.target_repo.suspend_write_group()
4022
4377
                return write_group_tokens, missing_keys
4023
 
        self.target_repo.commit_write_group()
 
4378
        hint = self.target_repo.commit_write_group()
 
4379
        if (to_serializer != src_serializer and
 
4380
            self.target_repo._format.pack_compresses):
 
4381
            self.target_repo.pack(hint=hint)
4024
4382
        return [], set()
4025
4383
 
4026
 
    def _extract_and_insert_inventories(self, substream, serializer):
 
4384
    def _extract_and_insert_inventory_deltas(self, substream, serializer):
 
4385
        target_rich_root = self.target_repo._format.rich_root_data
 
4386
        target_tree_refs = self.target_repo._format.supports_tree_reference
 
4387
        for record in substream:
 
4388
            # Insert the delta directly
 
4389
            inventory_delta_bytes = record.get_bytes_as('fulltext')
 
4390
            deserialiser = inventory_delta.InventoryDeltaDeserializer()
 
4391
            try:
 
4392
                parse_result = deserialiser.parse_text_bytes(
 
4393
                    inventory_delta_bytes)
 
4394
            except inventory_delta.IncompatibleInventoryDelta, err:
 
4395
                trace.mutter("Incompatible delta: %s", err.msg)
 
4396
                raise errors.IncompatibleRevision(self.target_repo._format)
 
4397
            basis_id, new_id, rich_root, tree_refs, inv_delta = parse_result
 
4398
            revision_id = new_id
 
4399
            parents = [key[0] for key in record.parents]
 
4400
            self.target_repo.add_inventory_by_delta(
 
4401
                basis_id, inv_delta, revision_id, parents)
 
4402
 
 
4403
    def _extract_and_insert_inventories(self, substream, serializer,
 
4404
            parse_delta=None):
4027
4405
        """Generate a new inventory versionedfile in target, converting data.
4028
4406
 
4029
4407
        The inventory is retrieved from the source, (deserializing it), and
4030
4408
        stored in the target (reserializing it in a different format).
4031
4409
        """
 
4410
        target_rich_root = self.target_repo._format.rich_root_data
 
4411
        target_tree_refs = self.target_repo._format.supports_tree_reference
4032
4412
        for record in substream:
 
4413
            # It's not a delta, so it must be a fulltext in the source
 
4414
            # serializer's format.
4033
4415
            bytes = record.get_bytes_as('fulltext')
4034
4416
            revision_id = record.key[0]
4035
4417
            inv = serializer.read_inventory_from_string(bytes, revision_id)
4036
4418
            parents = [key[0] for key in record.parents]
4037
4419
            self.target_repo.add_inventory(revision_id, inv, parents)
 
4420
            # No need to keep holding this full inv in memory when the rest of
 
4421
            # the substream is likely to be all deltas.
 
4422
            del inv
4038
4423
 
4039
4424
    def _extract_and_insert_revisions(self, substream, serializer):
4040
4425
        for record in substream:
4089
4474
        return [('signatures', signatures), ('revisions', revisions)]
4090
4475
 
4091
4476
    def _generate_root_texts(self, revs):
4092
 
        """This will be called by __fetch between fetching weave texts and
 
4477
        """This will be called by get_stream between fetching weave texts and
4093
4478
        fetching the inventory weave.
4094
 
 
4095
 
        Subclasses should override this if they need to generate root texts
4096
 
        after fetching weave texts.
4097
4479
        """
4098
4480
        if self._rich_root_upgrade():
4099
 
            import bzrlib.fetch
4100
 
            return bzrlib.fetch.Inter1and2Helper(
 
4481
            return _mod_fetch.Inter1and2Helper(
4101
4482
                self.from_repository).generate_root_texts(revs)
4102
4483
        else:
4103
4484
            return []
4106
4487
        phase = 'file'
4107
4488
        revs = search.get_keys()
4108
4489
        graph = self.from_repository.get_graph()
4109
 
        revs = list(graph.iter_topo_order(revs))
 
4490
        revs = tsort.topo_sort(graph.get_parent_map(revs))
4110
4491
        data_to_fetch = self.from_repository.item_keys_introduced_by(revs)
4111
4492
        text_keys = []
4112
4493
        for knit_kind, file_id, revisions in data_to_fetch:
4131
4512
                # will be valid.
4132
4513
                for _ in self._generate_root_texts(revs):
4133
4514
                    yield _
4134
 
                # NB: This currently reopens the inventory weave in source;
4135
 
                # using a single stream interface instead would avoid this.
4136
 
                from_weave = self.from_repository.inventories
4137
4515
                # we fetch only the referenced inventories because we do not
4138
4516
                # know for unselected inventories whether all their required
4139
4517
                # texts are present in the other repository - it could be
4158
4536
        keys['texts'] = set()
4159
4537
        keys['revisions'] = set()
4160
4538
        keys['inventories'] = set()
 
4539
        keys['chk_bytes'] = set()
4161
4540
        keys['signatures'] = set()
4162
4541
        for key in missing_keys:
4163
4542
            keys[key[0]].add(key[1:])
4170
4549
                    keys['revisions'],))
4171
4550
        for substream_kind, keys in keys.iteritems():
4172
4551
            vf = getattr(self.from_repository, substream_kind)
 
4552
            if vf is None and keys:
 
4553
                    raise AssertionError(
 
4554
                        "cannot fill in keys for a versioned file we don't"
 
4555
                        " have: %s needs %s" % (substream_kind, keys))
 
4556
            if not keys:
 
4557
                # No need to stream something we don't have
 
4558
                continue
 
4559
            if substream_kind == 'inventories':
 
4560
                # Some missing keys are genuinely ghosts, filter those out.
 
4561
                present = self.from_repository.inventories.get_parent_map(keys)
 
4562
                revs = [key[0] for key in present]
 
4563
                # Get the inventory stream more-or-less as we do for the
 
4564
                # original stream; there's no reason to assume that records
 
4565
                # direct from the source will be suitable for the sink.  (Think
 
4566
                # e.g. 2a -> 1.9-rich-root).
 
4567
                for info in self._get_inventory_stream(revs, missing=True):
 
4568
                    yield info
 
4569
                continue
 
4570
 
4173
4571
            # Ask for full texts always so that we don't need more round trips
4174
4572
            # after this stream.
4175
 
            stream = vf.get_record_stream(keys,
4176
 
                self.to_format._fetch_order, True)
 
4573
            # Some of the missing keys are genuinely ghosts, so filter absent
 
4574
            # records. The Sink is responsible for doing another check to
 
4575
            # ensure that ghosts don't introduce missing data for future
 
4576
            # fetches.
 
4577
            stream = versionedfile.filter_absent(vf.get_record_stream(keys,
 
4578
                self.to_format._fetch_order, True))
4177
4579
            yield substream_kind, stream
4178
4580
 
4179
4581
    def inventory_fetch_order(self):
4186
4588
        return (not self.from_repository._format.rich_root_data and
4187
4589
            self.to_format.rich_root_data)
4188
4590
 
4189
 
    def _get_inventory_stream(self, revision_ids):
 
4591
    def _get_inventory_stream(self, revision_ids, missing=False):
4190
4592
        from_format = self.from_repository._format
4191
 
        if (from_format.supports_chks and self.to_format.supports_chks
4192
 
            and (from_format._serializer == self.to_format._serializer)):
4193
 
            # Both sides support chks, and they use the same serializer, so it
4194
 
            # is safe to transmit the chk pages and inventory pages across
4195
 
            # as-is.
4196
 
            return self._get_chk_inventory_stream(revision_ids)
4197
 
        elif (not from_format.supports_chks):
4198
 
            # Source repository doesn't support chks. So we can transmit the
4199
 
            # inventories 'as-is' and either they are just accepted on the
4200
 
            # target, or the Sink will properly convert it.
4201
 
            return self._get_simple_inventory_stream(revision_ids)
 
4593
        if (from_format.supports_chks and self.to_format.supports_chks and
 
4594
            from_format.network_name() == self.to_format.network_name()):
 
4595
            raise AssertionError(
 
4596
                "this case should be handled by GroupCHKStreamSource")
 
4597
        elif 'forceinvdeltas' in debug.debug_flags:
 
4598
            return self._get_convertable_inventory_stream(revision_ids,
 
4599
                    delta_versus_null=missing)
 
4600
        elif from_format.network_name() == self.to_format.network_name():
 
4601
            # Same format.
 
4602
            return self._get_simple_inventory_stream(revision_ids,
 
4603
                    missing=missing)
 
4604
        elif (not from_format.supports_chks and not self.to_format.supports_chks
 
4605
                and from_format._serializer == self.to_format._serializer):
 
4606
            # Essentially the same format.
 
4607
            return self._get_simple_inventory_stream(revision_ids,
 
4608
                    missing=missing)
4202
4609
        else:
4203
 
            # XXX: Hack to make not-chk->chk fetch: copy the inventories as
4204
 
            #      inventories. Note that this should probably be done somehow
4205
 
            #      as part of bzrlib.repository.StreamSink. Except JAM couldn't
4206
 
            #      figure out how a non-chk repository could possibly handle
4207
 
            #      deserializing an inventory stream from a chk repo, as it
4208
 
            #      doesn't have a way to understand individual pages.
4209
 
            return self._get_convertable_inventory_stream(revision_ids)
 
4610
            # Any time we switch serializations, we want to use an
 
4611
            # inventory-delta based approach.
 
4612
            return self._get_convertable_inventory_stream(revision_ids,
 
4613
                    delta_versus_null=missing)
4210
4614
 
4211
 
    def _get_simple_inventory_stream(self, revision_ids):
 
4615
    def _get_simple_inventory_stream(self, revision_ids, missing=False):
 
4616
        # NB: This currently reopens the inventory weave in source;
 
4617
        # using a single stream interface instead would avoid this.
4212
4618
        from_weave = self.from_repository.inventories
 
4619
        if missing:
 
4620
            delta_closure = True
 
4621
        else:
 
4622
            delta_closure = not self.delta_on_metadata()
4213
4623
        yield ('inventories', from_weave.get_record_stream(
4214
4624
            [(rev_id,) for rev_id in revision_ids],
4215
 
            self.inventory_fetch_order(),
4216
 
            not self.delta_on_metadata()))
4217
 
 
4218
 
    def _get_chk_inventory_stream(self, revision_ids):
4219
 
        """Fetch the inventory texts, along with the associated chk maps."""
4220
 
        # We want an inventory outside of the search set, so that we can filter
4221
 
        # out uninteresting chk pages. For now we use
4222
 
        # _find_revision_outside_set, but if we had a Search with cut_revs, we
4223
 
        # could use that instead.
4224
 
        start_rev_id = self.from_repository._find_revision_outside_set(
4225
 
                            revision_ids)
4226
 
        start_rev_key = (start_rev_id,)
4227
 
        inv_keys_to_fetch = [(rev_id,) for rev_id in revision_ids]
4228
 
        if start_rev_id != _mod_revision.NULL_REVISION:
4229
 
            inv_keys_to_fetch.append((start_rev_id,))
4230
 
        # Any repo that supports chk_bytes must also support out-of-order
4231
 
        # insertion. At least, that is how we expect it to work
4232
 
        # We use get_record_stream instead of iter_inventories because we want
4233
 
        # to be able to insert the stream as well. We could instead fetch
4234
 
        # allowing deltas, and then iter_inventories, but we don't know whether
4235
 
        # source or target is more 'local' anway.
4236
 
        inv_stream = self.from_repository.inventories.get_record_stream(
4237
 
            inv_keys_to_fetch, 'unordered',
4238
 
            True) # We need them as full-texts so we can find their references
4239
 
        uninteresting_chk_roots = set()
4240
 
        interesting_chk_roots = set()
4241
 
        def filter_inv_stream(inv_stream):
4242
 
            for idx, record in enumerate(inv_stream):
4243
 
                ### child_pb.update('fetch inv', idx, len(inv_keys_to_fetch))
4244
 
                bytes = record.get_bytes_as('fulltext')
4245
 
                chk_inv = inventory.CHKInventory.deserialise(
4246
 
                    self.from_repository.chk_bytes, bytes, record.key)
4247
 
                if record.key == start_rev_key:
4248
 
                    uninteresting_chk_roots.add(chk_inv.id_to_entry.key())
4249
 
                    p_id_map = chk_inv.parent_id_basename_to_file_id
4250
 
                    if p_id_map is not None:
4251
 
                        uninteresting_chk_roots.add(p_id_map.key())
4252
 
                else:
4253
 
                    yield record
4254
 
                    interesting_chk_roots.add(chk_inv.id_to_entry.key())
4255
 
                    p_id_map = chk_inv.parent_id_basename_to_file_id
4256
 
                    if p_id_map is not None:
4257
 
                        interesting_chk_roots.add(p_id_map.key())
4258
 
        ### pb.update('fetch inventory', 0, 2)
4259
 
        yield ('inventories', filter_inv_stream(inv_stream))
4260
 
        # Now that we have worked out all of the interesting root nodes, grab
4261
 
        # all of the interesting pages and insert them
4262
 
        ### pb.update('fetch inventory', 1, 2)
4263
 
        interesting = chk_map.iter_interesting_nodes(
4264
 
            self.from_repository.chk_bytes, interesting_chk_roots,
4265
 
            uninteresting_chk_roots)
4266
 
        def to_stream_adapter():
4267
 
            """Adapt the iter_interesting_nodes result to a single stream.
4268
 
 
4269
 
            iter_interesting_nodes returns records as it processes them, along
4270
 
            with keys. However, we only want to return the records themselves.
4271
 
            """
4272
 
            for record, items in interesting:
4273
 
                if record is not None:
4274
 
                    yield record
4275
 
        # XXX: We could instead call get_record_stream(records.keys())
4276
 
        #      ATM, this will always insert the records as fulltexts, and
4277
 
        #      requires that you can hang on to records once you have gone
4278
 
        #      on to the next one. Further, it causes the target to
4279
 
        #      recompress the data. Testing shows it to be faster than
4280
 
        #      requesting the records again, though.
4281
 
        yield ('chk_bytes', to_stream_adapter())
4282
 
        ### pb.update('fetch inventory', 2, 2)
4283
 
 
4284
 
    def _get_convertable_inventory_stream(self, revision_ids):
4285
 
        # XXX: One of source or target is using chks, and they don't have
4286
 
        #      compatible serializations. The StreamSink code expects to be
4287
 
        #      able to convert on the target, so we need to put
4288
 
        #      bytes-on-the-wire that can be converted
4289
 
        yield ('inventories', self._stream_invs_as_fulltexts(revision_ids))
4290
 
 
4291
 
    def _stream_invs_as_fulltexts(self, revision_ids):
 
4625
            self.inventory_fetch_order(), delta_closure))
 
4626
 
 
4627
    def _get_convertable_inventory_stream(self, revision_ids,
 
4628
                                          delta_versus_null=False):
 
4629
        # The source is using CHKs, but the target either doesn't or it has a
 
4630
        # different serializer.  The StreamSink code expects to be able to
 
4631
        # convert on the target, so we need to put bytes-on-the-wire that can
 
4632
        # be converted.  That means inventory deltas (if the remote is <1.19,
 
4633
        # RemoteStreamSink will fallback to VFS to insert the deltas).
 
4634
        yield ('inventory-deltas',
 
4635
           self._stream_invs_as_deltas(revision_ids,
 
4636
                                       delta_versus_null=delta_versus_null))
 
4637
 
 
4638
    def _stream_invs_as_deltas(self, revision_ids, delta_versus_null=False):
 
4639
        """Return a stream of inventory-deltas for the given rev ids.
 
4640
 
 
4641
        :param revision_ids: The list of inventories to transmit
 
4642
        :param delta_versus_null: Don't try to find a minimal delta for this
 
4643
            entry, instead compute the delta versus the NULL_REVISION. This
 
4644
            effectively streams a complete inventory. Used for stuff like
 
4645
            filling in missing parents, etc.
 
4646
        """
4292
4647
        from_repo = self.from_repository
4293
 
        from_serializer = from_repo._format._serializer
4294
4648
        revision_keys = [(rev_id,) for rev_id in revision_ids]
4295
4649
        parent_map = from_repo.inventories.get_parent_map(revision_keys)
4296
 
        for inv in self.from_repository.iter_inventories(revision_ids):
4297
 
            # XXX: This is a bit hackish, but it works. Basically,
4298
 
            #      CHKSerializer 'accidentally' supports
4299
 
            #      read/write_inventory_to_string, even though that is never
4300
 
            #      the format that is stored on disk. It *does* give us a
4301
 
            #      single string representation for an inventory, so live with
4302
 
            #      it for now.
4303
 
            #      This would be far better if we had a 'serialized inventory
4304
 
            #      delta' form. Then we could use 'inventory._make_delta', and
4305
 
            #      transmit that. This would both be faster to generate, and
4306
 
            #      result in fewer bytes-on-the-wire.
4307
 
            as_bytes = from_serializer.write_inventory_to_string(inv)
 
4650
        # XXX: possibly repos could implement a more efficient iter_inv_deltas
 
4651
        # method...
 
4652
        inventories = self.from_repository.iter_inventories(
 
4653
            revision_ids, 'topological')
 
4654
        format = from_repo._format
 
4655
        invs_sent_so_far = set([_mod_revision.NULL_REVISION])
 
4656
        inventory_cache = lru_cache.LRUCache(50)
 
4657
        null_inventory = from_repo.revision_tree(
 
4658
            _mod_revision.NULL_REVISION).inventory
 
4659
        # XXX: ideally the rich-root/tree-refs flags would be per-revision, not
 
4660
        # per-repo (e.g.  streaming a non-rich-root revision out of a rich-root
 
4661
        # repo back into a non-rich-root repo ought to be allowed)
 
4662
        serializer = inventory_delta.InventoryDeltaSerializer(
 
4663
            versioned_root=format.rich_root_data,
 
4664
            tree_references=format.supports_tree_reference)
 
4665
        for inv in inventories:
4308
4666
            key = (inv.revision_id,)
4309
4667
            parent_keys = parent_map.get(key, ())
 
4668
            delta = None
 
4669
            if not delta_versus_null and parent_keys:
 
4670
                # The caller did not ask for complete inventories and we have
 
4671
                # some parents that we can delta against.  Make a delta against
 
4672
                # each parent so that we can find the smallest.
 
4673
                parent_ids = [parent_key[0] for parent_key in parent_keys]
 
4674
                for parent_id in parent_ids:
 
4675
                    if parent_id not in invs_sent_so_far:
 
4676
                        # We don't know that the remote side has this basis, so
 
4677
                        # we can't use it.
 
4678
                        continue
 
4679
                    if parent_id == _mod_revision.NULL_REVISION:
 
4680
                        parent_inv = null_inventory
 
4681
                    else:
 
4682
                        parent_inv = inventory_cache.get(parent_id, None)
 
4683
                        if parent_inv is None:
 
4684
                            parent_inv = from_repo.get_inventory(parent_id)
 
4685
                    candidate_delta = inv._make_delta(parent_inv)
 
4686
                    if (delta is None or
 
4687
                        len(delta) > len(candidate_delta)):
 
4688
                        delta = candidate_delta
 
4689
                        basis_id = parent_id
 
4690
            if delta is None:
 
4691
                # Either none of the parents ended up being suitable, or we
 
4692
                # were asked to delta against NULL
 
4693
                basis_id = _mod_revision.NULL_REVISION
 
4694
                delta = inv._make_delta(null_inventory)
 
4695
            invs_sent_so_far.add(inv.revision_id)
 
4696
            inventory_cache[inv.revision_id] = inv
 
4697
            delta_serialized = ''.join(
 
4698
                serializer.delta_to_lines(basis_id, key[-1], delta))
4310
4699
            yield versionedfile.FulltextContentFactory(
4311
 
                key, parent_keys, None, as_bytes)
 
4700
                key, parent_keys, None, delta_serialized)
 
4701
 
 
4702
 
 
4703
def _iter_for_revno(repo, partial_history_cache, stop_index=None,
 
4704
                    stop_revision=None):
 
4705
    """Extend the partial history to include a given index
 
4706
 
 
4707
    If a stop_index is supplied, stop when that index has been reached.
 
4708
    If a stop_revision is supplied, stop when that revision is
 
4709
    encountered.  Otherwise, stop when the beginning of history is
 
4710
    reached.
 
4711
 
 
4712
    :param stop_index: The index which should be present.  When it is
 
4713
        present, history extension will stop.
 
4714
    :param stop_revision: The revision id which should be present.  When
 
4715
        it is encountered, history extension will stop.
 
4716
    """
 
4717
    start_revision = partial_history_cache[-1]
 
4718
    iterator = repo.iter_reverse_revision_history(start_revision)
 
4719
    try:
 
4720
        #skip the last revision in the list
 
4721
        iterator.next()
 
4722
        while True:
 
4723
            if (stop_index is not None and
 
4724
                len(partial_history_cache) > stop_index):
 
4725
                break
 
4726
            if partial_history_cache[-1] == stop_revision:
 
4727
                break
 
4728
            revision_id = iterator.next()
 
4729
            partial_history_cache.append(revision_id)
 
4730
    except StopIteration:
 
4731
        # No more history
 
4732
        return
4312
4733