~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/groupcompress.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-03-25 00:02:51 UTC
  • mfrom: (5106.1.1 version-bump)
  • Revision ID: pqm@pqm.ubuntu.com-20100325000251-bwsv5c5d3l9x3lnn
(Jelmer) Bump API version for 2.2.0.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008-2011 Canonical Ltd
 
1
# Copyright (C) 2008, 2009, 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
16
16
 
17
17
"""Core compression logic for compressing streams of related files."""
18
18
 
19
 
from __future__ import absolute_import
20
 
 
21
19
import time
22
20
import zlib
 
21
try:
 
22
    import pylzma
 
23
except ImportError:
 
24
    pylzma = None
23
25
 
24
 
from bzrlib.lazy_import import lazy_import
25
 
lazy_import(globals(), """
26
26
from bzrlib import (
27
27
    annotate,
28
 
    config,
29
28
    debug,
30
29
    errors,
31
30
    graph as _mod_graph,
 
31
    knit,
32
32
    osutils,
33
33
    pack,
34
34
    static_tuple,
35
35
    trace,
36
 
    tsort,
37
36
    )
38
 
 
39
 
from bzrlib.repofmt import pack_repo
40
 
from bzrlib.i18n import gettext
41
 
""")
42
 
 
43
37
from bzrlib.btree_index import BTreeBuilder
44
38
from bzrlib.lru_cache import LRUSizeCache
 
39
from bzrlib.tsort import topo_sort
45
40
from bzrlib.versionedfile import (
46
 
    _KeyRefs,
47
41
    adapter_registry,
48
42
    AbsentContentFactory,
49
43
    ChunkedContentFactory,
50
44
    FulltextContentFactory,
51
 
    VersionedFilesWithFallbacks,
 
45
    VersionedFiles,
52
46
    )
53
47
 
54
48
# Minimum number of uncompressed bytes to try fetch at once when retrieving
55
49
# groupcompress blocks.
56
50
BATCH_SIZE = 2**16
57
51
 
 
52
_USE_LZMA = False and (pylzma is not None)
 
53
 
58
54
# osutils.sha_string('')
59
55
_null_sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
60
56
 
81
77
 
82
78
    present_keys = []
83
79
    for prefix in sorted(per_prefix_map):
84
 
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
 
80
        present_keys.extend(reversed(topo_sort(per_prefix_map[prefix])))
85
81
    return present_keys
86
82
 
87
83
 
105
101
    def __init__(self):
106
102
        # map by key? or just order in file?
107
103
        self._compressor_name = None
108
 
        self._z_content_chunks = None
 
104
        self._z_content = None
109
105
        self._z_content_decompressor = None
110
106
        self._z_content_length = None
111
107
        self._content_length = None
139
135
                self._content = ''.join(self._content_chunks)
140
136
                self._content_chunks = None
141
137
        if self._content is None:
142
 
            # We join self._z_content_chunks here, because if we are
143
 
            # decompressing, then it is *very* likely that we have a single
144
 
            # chunk
145
 
            if self._z_content_chunks is None:
 
138
            if self._z_content is None:
146
139
                raise AssertionError('No content to decompress')
147
 
            z_content = ''.join(self._z_content_chunks)
148
 
            if z_content == '':
 
140
            if self._z_content == '':
149
141
                self._content = ''
150
142
            elif self._compressor_name == 'lzma':
151
143
                # We don't do partial lzma decomp yet
152
 
                import pylzma
153
 
                self._content = pylzma.decompress(z_content)
 
144
                self._content = pylzma.decompress(self._z_content)
154
145
            elif self._compressor_name == 'zlib':
155
146
                # Start a zlib decompressor
156
147
                if num_bytes * 4 > self._content_length * 3:
157
148
                    # If we are requesting more that 3/4ths of the content,
158
149
                    # just extract the whole thing in a single pass
159
150
                    num_bytes = self._content_length
160
 
                    self._content = zlib.decompress(z_content)
 
151
                    self._content = zlib.decompress(self._z_content)
161
152
                else:
162
153
                    self._z_content_decompressor = zlib.decompressobj()
163
154
                    # Seed the decompressor with the uncompressed bytes, so
164
155
                    # that the rest of the code is simplified
165
156
                    self._content = self._z_content_decompressor.decompress(
166
 
                        z_content, num_bytes + _ZLIB_DECOMP_WINDOW)
 
157
                        self._z_content, num_bytes + _ZLIB_DECOMP_WINDOW)
167
158
                    if not self._z_content_decompressor.unconsumed_tail:
168
159
                        self._z_content_decompressor = None
169
160
            else:
216
207
            # XXX: Define some GCCorrupt error ?
217
208
            raise AssertionError('Invalid bytes: (%d) != %d + %d' %
218
209
                                 (len(bytes), pos, self._z_content_length))
219
 
        self._z_content_chunks = (bytes[pos:],)
220
 
 
221
 
    @property
222
 
    def _z_content(self):
223
 
        """Return z_content_chunks as a simple string.
224
 
 
225
 
        Meant only to be used by the test suite.
226
 
        """
227
 
        if self._z_content_chunks is not None:
228
 
            return ''.join(self._z_content_chunks)
229
 
        return None
 
210
        self._z_content = bytes[pos:]
230
211
 
231
212
    @classmethod
232
213
    def from_bytes(cls, bytes):
288
269
        self._content_length = length
289
270
        self._content_chunks = content_chunks
290
271
        self._content = None
291
 
        self._z_content_chunks = None
 
272
        self._z_content = None
292
273
 
293
274
    def set_content(self, content):
294
275
        """Set the content of this block."""
295
276
        self._content_length = len(content)
296
277
        self._content = content
297
 
        self._z_content_chunks = None
298
 
 
299
 
    def _create_z_content_from_chunks(self, chunks):
 
278
        self._z_content = None
 
279
 
 
280
    def _create_z_content_using_lzma(self):
 
281
        if self._content_chunks is not None:
 
282
            self._content = ''.join(self._content_chunks)
 
283
            self._content_chunks = None
 
284
        if self._content is None:
 
285
            raise AssertionError('Nothing to compress')
 
286
        self._z_content = pylzma.compress(self._content)
 
287
        self._z_content_length = len(self._z_content)
 
288
 
 
289
    def _create_z_content_from_chunks(self):
300
290
        compressor = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION)
301
 
        # Peak in this point is 1 fulltext, 1 compressed text, + zlib overhead
302
 
        # (measured peak is maybe 30MB over the above...)
303
 
        compressed_chunks = map(compressor.compress, chunks)
 
291
        compressed_chunks = map(compressor.compress, self._content_chunks)
304
292
        compressed_chunks.append(compressor.flush())
305
 
        # Ignore empty chunks
306
 
        self._z_content_chunks = [c for c in compressed_chunks if c]
307
 
        self._z_content_length = sum(map(len, self._z_content_chunks))
 
293
        self._z_content = ''.join(compressed_chunks)
 
294
        self._z_content_length = len(self._z_content)
308
295
 
309
296
    def _create_z_content(self):
310
 
        if self._z_content_chunks is not None:
 
297
        if self._z_content is not None:
 
298
            return
 
299
        if _USE_LZMA:
 
300
            self._create_z_content_using_lzma()
311
301
            return
312
302
        if self._content_chunks is not None:
313
 
            chunks = self._content_chunks
314
 
        else:
315
 
            chunks = (self._content,)
316
 
        self._create_z_content_from_chunks(chunks)
 
303
            self._create_z_content_from_chunks()
 
304
            return
 
305
        self._z_content = zlib.compress(self._content)
 
306
        self._z_content_length = len(self._z_content)
317
307
 
318
 
    def to_chunks(self):
319
 
        """Create the byte stream as a series of 'chunks'"""
 
308
    def to_bytes(self):
 
309
        """Encode the information into a byte stream."""
320
310
        self._create_z_content()
321
 
        header = self.GCB_HEADER
322
 
        chunks = ['%s%d\n%d\n'
323
 
                  % (header, self._z_content_length, self._content_length),
 
311
        if _USE_LZMA:
 
312
            header = self.GCB_LZ_HEADER
 
313
        else:
 
314
            header = self.GCB_HEADER
 
315
        chunks = [header,
 
316
                  '%d\n%d\n' % (self._z_content_length, self._content_length),
 
317
                  self._z_content,
324
318
                 ]
325
 
        chunks.extend(self._z_content_chunks)
326
 
        total_len = sum(map(len, chunks))
327
 
        return total_len, chunks
328
 
 
329
 
    def to_bytes(self):
330
 
        """Encode the information into a byte stream."""
331
 
        total_len, chunks = self.to_chunks()
332
319
        return ''.join(chunks)
333
320
 
334
321
    def _dump(self, include_text=False):
448
435
                # Grab and cache the raw bytes for this entry
449
436
                # and break the ref-cycle with _manager since we don't need it
450
437
                # anymore
451
 
                try:
452
 
                    self._manager._prepare_for_extract()
453
 
                except zlib.error as value:
454
 
                    raise errors.DecompressCorruption("zlib: " + str(value))
 
438
                self._manager._prepare_for_extract()
455
439
                block = self._manager._block
456
440
                self._bytes = block.extract(self.key, self._start, self._end)
457
441
                # There are code paths that first extract as fulltext, and then
476
460
    _full_enough_block_size = 3*1024*1024 # size at which we won't repack
477
461
    _full_enough_mixed_block_size = 2*768*1024 # 1.5MB
478
462
 
479
 
    def __init__(self, block, get_compressor_settings=None):
 
463
    def __init__(self, block):
480
464
        self._block = block
481
465
        # We need to preserve the ordering
482
466
        self._factories = []
483
467
        self._last_byte = 0
484
 
        self._get_settings = get_compressor_settings
485
 
        self._compressor_settings = None
486
 
 
487
 
    def _get_compressor_settings(self):
488
 
        if self._compressor_settings is not None:
489
 
            return self._compressor_settings
490
 
        settings = None
491
 
        if self._get_settings is not None:
492
 
            settings = self._get_settings()
493
 
        if settings is None:
494
 
            vf = GroupCompressVersionedFiles
495
 
            settings = vf._DEFAULT_COMPRESSOR_SETTINGS
496
 
        self._compressor_settings = settings
497
 
        return self._compressor_settings
498
468
 
499
469
    def add_factory(self, key, parents, start, end):
500
470
        if not self._factories:
533
503
        new_block.set_content(self._block._content[:last_byte])
534
504
        self._block = new_block
535
505
 
536
 
    def _make_group_compressor(self):
537
 
        return GroupCompressor(self._get_compressor_settings())
538
 
 
539
506
    def _rebuild_block(self):
540
507
        """Create a new GroupCompressBlock with only the referenced texts."""
541
 
        compressor = self._make_group_compressor()
 
508
        compressor = GroupCompressor()
542
509
        tstart = time.time()
543
510
        old_length = self._block._content_length
544
511
        end_point = 0
556
523
        #       block? It seems hard to come up with a method that it would
557
524
        #       expand, since we do full compression again. Perhaps based on a
558
525
        #       request that ends up poorly ordered?
559
 
        # TODO: If the content would have expanded, then we would want to
560
 
        #       handle a case where we need to split the block.
561
 
        #       Now that we have a user-tweakable option
562
 
        #       (max_bytes_to_index), it is possible that one person set it
563
 
        #       to a very low value, causing poor compression.
564
526
        delta = time.time() - tstart
565
527
        self._block = new_block
566
528
        trace.mutter('creating new compressed block on-the-fly in %.3fs'
717
679
        z_header_bytes = zlib.compress(header_bytes)
718
680
        del header_bytes
719
681
        z_header_bytes_len = len(z_header_bytes)
720
 
        block_bytes_len, block_chunks = self._block.to_chunks()
 
682
        block_bytes = self._block.to_bytes()
721
683
        lines.append('%d\n%d\n%d\n' % (z_header_bytes_len, header_bytes_len,
722
 
                                       block_bytes_len))
 
684
                                       len(block_bytes)))
723
685
        lines.append(z_header_bytes)
724
 
        lines.extend(block_chunks)
725
 
        del z_header_bytes, block_chunks
726
 
        # TODO: This is a point where we will double the memory consumption. To
727
 
        #       avoid this, we probably have to switch to a 'chunked' api
 
686
        lines.append(block_bytes)
 
687
        del z_header_bytes, block_bytes
728
688
        return ''.join(lines)
729
689
 
730
690
    @classmethod
731
691
    def from_bytes(cls, bytes):
732
692
        # TODO: This does extra string copying, probably better to do it a
733
 
        #       different way. At a minimum this creates 2 copies of the
734
 
        #       compressed content
 
693
        #       different way
735
694
        (storage_kind, z_header_len, header_len,
736
695
         block_len, rest) = bytes.split('\n', 4)
737
696
        del bytes
789
748
 
790
749
class _CommonGroupCompressor(object):
791
750
 
792
 
    def __init__(self, settings=None):
 
751
    def __init__(self):
793
752
        """Create a GroupCompressor."""
794
753
        self.chunks = []
795
754
        self._last = None
798
757
        self.labels_deltas = {}
799
758
        self._delta_index = None # Set by the children
800
759
        self._block = GroupCompressBlock()
801
 
        if settings is None:
802
 
            self._settings = {}
803
 
        else:
804
 
            self._settings = settings
805
760
 
806
761
    def compress(self, key, bytes, expected_sha, nostore_sha=None, soft=False):
807
762
        """Compress lines with label key.
899
854
 
900
855
        After calling this, the compressor should no longer be used
901
856
        """
 
857
        # TODO: this causes us to 'bloat' to 2x the size of content in the
 
858
        #       group. This has an impact for 'commit' of large objects.
 
859
        #       One possibility is to use self._content_chunks, and be lazy and
 
860
        #       only fill out self._content as a full string when we actually
 
861
        #       need it. That would at least drop the peak memory consumption
 
862
        #       for 'commit' down to ~1x the size of the largest file, at a
 
863
        #       cost of increased complexity within this code. 2x is still <<
 
864
        #       3x the size of the largest file, so we are doing ok.
902
865
        self._block.set_chunked_content(self.chunks, self.endpoint)
903
866
        self.chunks = None
904
867
        self._delta_index = None
922
885
 
923
886
class PythonGroupCompressor(_CommonGroupCompressor):
924
887
 
925
 
    def __init__(self, settings=None):
 
888
    def __init__(self):
926
889
        """Create a GroupCompressor.
927
890
 
928
891
        Used only if the pyrex version is not available.
929
892
        """
930
 
        super(PythonGroupCompressor, self).__init__(settings)
 
893
        super(PythonGroupCompressor, self).__init__()
931
894
        self._delta_index = LinesDeltaIndex([])
932
895
        # The actual content is managed by LinesDeltaIndex
933
896
        self.chunks = self._delta_index.lines
970
933
 
971
934
    It contains code very similar to SequenceMatcher because of having a similar
972
935
    task. However some key differences apply:
973
 
 
974
 
    * there is no junk, we want a minimal edit not a human readable diff.
975
 
    * we don't filter very common lines (because we don't know where a good
976
 
      range will start, and after the first text we want to be emitting minmal
977
 
      edits only.
978
 
    * we chain the left side, not the right side
979
 
    * we incrementally update the adjacency matrix as new lines are provided.
980
 
    * we look for matches in all of the left side, so the routine which does
981
 
      the analagous task of find_longest_match does not need to filter on the
982
 
      left side.
 
936
     - there is no junk, we want a minimal edit not a human readable diff.
 
937
     - we don't filter very common lines (because we don't know where a good
 
938
       range will start, and after the first text we want to be emitting minmal
 
939
       edits only.
 
940
     - we chain the left side, not the right side
 
941
     - we incrementally update the adjacency matrix as new lines are provided.
 
942
     - we look for matches in all of the left side, so the routine which does
 
943
       the analagous task of find_longest_match does not need to filter on the
 
944
       left side.
983
945
    """
984
946
 
985
 
    def __init__(self, settings=None):
986
 
        super(PyrexGroupCompressor, self).__init__(settings)
987
 
        max_bytes_to_index = self._settings.get('max_bytes_to_index', 0)
988
 
        self._delta_index = DeltaIndex(max_bytes_to_index=max_bytes_to_index)
 
947
    def __init__(self):
 
948
        super(PyrexGroupCompressor, self).__init__()
 
949
        self._delta_index = DeltaIndex()
989
950
 
990
951
    def _compress(self, key, bytes, max_delta_size, soft=False):
991
952
        """see _CommonGroupCompressor._compress"""
1066
1027
        index = _GCGraphIndex(graph_index, lambda:True, parents=parents,
1067
1028
            add_callback=graph_index.add_nodes,
1068
1029
            inconsistency_fatal=inconsistency_fatal)
1069
 
        access = pack_repo._DirectPackAccess({})
 
1030
        access = knit._DirectPackAccess({})
1070
1031
        access.set_writer(writer, graph_index, (transport, 'newpack'))
1071
1032
        result = GroupCompressVersionedFiles(index, access, delta)
1072
1033
        result.stream = stream
1082
1043
 
1083
1044
class _BatchingBlockFetcher(object):
1084
1045
    """Fetch group compress blocks in batches.
1085
 
 
 
1046
    
1086
1047
    :ivar total_bytes: int of expected number of bytes needed to fetch the
1087
1048
        currently pending batch.
1088
1049
    """
1089
1050
 
1090
 
    def __init__(self, gcvf, locations, get_compressor_settings=None):
 
1051
    def __init__(self, gcvf, locations):
1091
1052
        self.gcvf = gcvf
1092
1053
        self.locations = locations
1093
1054
        self.keys = []
1096
1057
        self.total_bytes = 0
1097
1058
        self.last_read_memo = None
1098
1059
        self.manager = None
1099
 
        self._get_compressor_settings = get_compressor_settings
1100
1060
 
1101
1061
    def add_key(self, key):
1102
1062
        """Add another to key to fetch.
1103
 
 
 
1063
        
1104
1064
        :return: The estimated number of bytes needed to fetch the batch so
1105
1065
            far.
1106
1066
        """
1131
1091
            # and then.
1132
1092
            self.batch_memos[read_memo] = cached_block
1133
1093
        return self.total_bytes
1134
 
 
 
1094
        
1135
1095
    def _flush_manager(self):
1136
1096
        if self.manager is not None:
1137
1097
            for factory in self.manager.get_record_stream():
1142
1102
    def yield_factories(self, full_flush=False):
1143
1103
        """Yield factories for keys added since the last yield.  They will be
1144
1104
        returned in the order they were added via add_key.
1145
 
 
 
1105
        
1146
1106
        :param full_flush: by default, some results may not be returned in case
1147
1107
            they can be part of the next batch.  If full_flush is True, then
1148
1108
            all results are returned.
1176
1136
                    memos_to_get_stack.pop()
1177
1137
                else:
1178
1138
                    block = self.batch_memos[read_memo]
1179
 
                self.manager = _LazyGroupContentManager(block,
1180
 
                    get_compressor_settings=self._get_compressor_settings)
 
1139
                self.manager = _LazyGroupContentManager(block)
1181
1140
                self.last_read_memo = read_memo
1182
1141
            start, end = index_memo[3:5]
1183
1142
            self.manager.add_factory(key, parents, start, end)
1190
1149
        self.total_bytes = 0
1191
1150
 
1192
1151
 
1193
 
class GroupCompressVersionedFiles(VersionedFilesWithFallbacks):
 
1152
class GroupCompressVersionedFiles(VersionedFiles):
1194
1153
    """A group-compress based VersionedFiles implementation."""
1195
1154
 
1196
 
    # This controls how the GroupCompress DeltaIndex works. Basically, we
1197
 
    # compute hash pointers into the source blocks (so hash(text) => text).
1198
 
    # However each of these references costs some memory in trade against a
1199
 
    # more accurate match result. For very large files, they either are
1200
 
    # pre-compressed and change in bulk whenever they change, or change in just
1201
 
    # local blocks. Either way, 'improved resolution' is not very helpful,
1202
 
    # versus running out of memory trying to track everything. The default max
1203
 
    # gives 100% sampling of a 1MB file.
1204
 
    _DEFAULT_MAX_BYTES_TO_INDEX = 1024 * 1024
1205
 
    _DEFAULT_COMPRESSOR_SETTINGS = {'max_bytes_to_index':
1206
 
                                     _DEFAULT_MAX_BYTES_TO_INDEX}
1207
 
 
1208
 
    def __init__(self, index, access, delta=True, _unadded_refs=None,
1209
 
                 _group_cache=None):
 
1155
    def __init__(self, index, access, delta=True, _unadded_refs=None):
1210
1156
        """Create a GroupCompressVersionedFiles object.
1211
1157
 
1212
1158
        :param index: The index object storing access and graph data.
1213
1159
        :param access: The access object storing raw data.
1214
1160
        :param delta: Whether to delta compress or just entropy compress.
1215
1161
        :param _unadded_refs: private parameter, don't use.
1216
 
        :param _group_cache: private parameter, don't use.
1217
1162
        """
1218
1163
        self._index = index
1219
1164
        self._access = access
1221
1166
        if _unadded_refs is None:
1222
1167
            _unadded_refs = {}
1223
1168
        self._unadded_refs = _unadded_refs
1224
 
        if _group_cache is None:
1225
 
            _group_cache = LRUSizeCache(max_size=50*1024*1024)
1226
 
        self._group_cache = _group_cache
1227
 
        self._immediate_fallback_vfs = []
1228
 
        self._max_bytes_to_index = None
 
1169
        self._group_cache = LRUSizeCache(max_size=50*1024*1024)
 
1170
        self._fallback_vfs = []
1229
1171
 
1230
1172
    def without_fallbacks(self):
1231
1173
        """Return a clone of this object without any fallbacks configured."""
1232
1174
        return GroupCompressVersionedFiles(self._index, self._access,
1233
 
            self._delta, _unadded_refs=dict(self._unadded_refs),
1234
 
            _group_cache=self._group_cache)
 
1175
            self._delta, _unadded_refs=dict(self._unadded_refs))
1235
1176
 
1236
1177
    def add_lines(self, key, parents, lines, parent_texts=None,
1237
1178
        left_matching_blocks=None, nostore_sha=None, random_id=False,
1241
1182
        :param key: The key tuple of the text to add.
1242
1183
        :param parents: The parents key tuples of the text to add.
1243
1184
        :param lines: A list of lines. Each line must be a bytestring. And all
1244
 
            of them except the last must be terminated with \\n and contain no
1245
 
            other \\n's. The last line may either contain no \\n's or a single
1246
 
            terminating \\n. If the lines list does meet this constraint the
1247
 
            add routine may error or may succeed - but you will be unable to
1248
 
            read the data back accurately. (Checking the lines have been split
 
1185
            of them except the last must be terminated with \n and contain no
 
1186
            other \n's. The last line may either contain no \n's or a single
 
1187
            terminating \n. If the lines list does meet this constraint the add
 
1188
            routine may error or may succeed - but you will be unable to read
 
1189
            the data back accurately. (Checking the lines have been split
1249
1190
            correctly is expensive and extremely unlikely to catch bugs so it
1250
1191
            is not done at runtime unless check_content is True.)
1251
1192
        :param parent_texts: An optional dictionary containing the opaque
1306
1247
 
1307
1248
        :param a_versioned_files: A VersionedFiles object.
1308
1249
        """
1309
 
        self._immediate_fallback_vfs.append(a_versioned_files)
 
1250
        self._fallback_vfs.append(a_versioned_files)
1310
1251
 
1311
1252
    def annotate(self, key):
1312
1253
        """See VersionedFiles.annotate."""
1346
1287
            self._check_lines_not_unicode(lines)
1347
1288
            self._check_lines_are_lines(lines)
1348
1289
 
 
1290
    def get_known_graph_ancestry(self, keys):
 
1291
        """Get a KnownGraph instance with the ancestry of keys."""
 
1292
        # Note that this is identical to
 
1293
        # KnitVersionedFiles.get_known_graph_ancestry, but they don't share
 
1294
        # ancestry.
 
1295
        parent_map, missing_keys = self._index.find_ancestry(keys)
 
1296
        for fallback in self._fallback_vfs:
 
1297
            if not missing_keys:
 
1298
                break
 
1299
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
 
1300
                                                missing_keys)
 
1301
            parent_map.update(f_parent_map)
 
1302
            missing_keys = f_missing_keys
 
1303
        kg = _mod_graph.KnownGraph(parent_map)
 
1304
        return kg
 
1305
 
1349
1306
    def get_parent_map(self, keys):
1350
1307
        """Get a map of the graph parents of keys.
1351
1308
 
1366
1323
            and so on.
1367
1324
        """
1368
1325
        result = {}
1369
 
        sources = [self._index] + self._immediate_fallback_vfs
 
1326
        sources = [self._index] + self._fallback_vfs
1370
1327
        source_results = []
1371
1328
        missing = set(keys)
1372
1329
        for source in sources:
1473
1430
        parent_map = {}
1474
1431
        key_to_source_map = {}
1475
1432
        source_results = []
1476
 
        for source in self._immediate_fallback_vfs:
 
1433
        for source in self._fallback_vfs:
1477
1434
            if not missing:
1478
1435
                break
1479
1436
            source_parents = source.get_parent_map(missing)
1489
1446
 
1490
1447
        The returned objects should be in the order defined by 'ordering',
1491
1448
        which can weave between different sources.
1492
 
 
1493
1449
        :param ordering: Must be one of 'topological' or 'groupcompress'
1494
1450
        :return: List of [(source, [keys])] tuples, such that all keys are in
1495
1451
            the defined order, regardless of source.
1496
1452
        """
1497
1453
        if ordering == 'topological':
1498
 
            present_keys = tsort.topo_sort(parent_map)
 
1454
            present_keys = topo_sort(parent_map)
1499
1455
        else:
1500
1456
            # ordering == 'groupcompress'
1501
1457
            # XXX: This only optimizes for the target ordering. We may need
1590
1546
        #  - we encounter an unadded ref, or
1591
1547
        #  - we run out of keys, or
1592
1548
        #  - the total bytes to retrieve for this batch > BATCH_SIZE
1593
 
        batcher = _BatchingBlockFetcher(self, locations,
1594
 
            get_compressor_settings=self._get_compressor_settings)
 
1549
        batcher = _BatchingBlockFetcher(self, locations)
1595
1550
        for source, keys in source_keys:
1596
1551
            if source is self:
1597
1552
                for key in keys:
1643
1598
        for _ in self._insert_record_stream(stream, random_id=False):
1644
1599
            pass
1645
1600
 
1646
 
    def _get_compressor_settings(self):
1647
 
        if self._max_bytes_to_index is None:
1648
 
            # TODO: VersionedFiles don't know about their containing
1649
 
            #       repository, so they don't have much of an idea about their
1650
 
            #       location. So for now, this is only a global option.
1651
 
            c = config.GlobalConfig()
1652
 
            val = c.get_user_option('bzr.groupcompress.max_bytes_to_index')
1653
 
            if val is not None:
1654
 
                try:
1655
 
                    val = int(val)
1656
 
                except ValueError, e:
1657
 
                    trace.warning('Value for '
1658
 
                                  '"bzr.groupcompress.max_bytes_to_index"'
1659
 
                                  ' %r is not an integer'
1660
 
                                  % (val,))
1661
 
                    val = None
1662
 
            if val is None:
1663
 
                val = self._DEFAULT_MAX_BYTES_TO_INDEX
1664
 
            self._max_bytes_to_index = val
1665
 
        return {'max_bytes_to_index': self._max_bytes_to_index}
1666
 
 
1667
 
    def _make_group_compressor(self):
1668
 
        return GroupCompressor(self._get_compressor_settings())
1669
 
 
1670
1601
    def _insert_record_stream(self, stream, random_id=False, nostore_sha=None,
1671
1602
                              reuse_blocks=True):
1672
1603
        """Internal core to insert a record stream into this container.
1695
1626
                return adapter
1696
1627
        # This will go up to fulltexts for gc to gc fetching, which isn't
1697
1628
        # ideal.
1698
 
        self._compressor = self._make_group_compressor()
 
1629
        self._compressor = GroupCompressor()
1699
1630
        self._unadded_refs = {}
1700
1631
        keys_to_add = []
1701
1632
        def flush():
1702
 
            bytes_len, chunks = self._compressor.flush().to_chunks()
1703
 
            self._compressor = self._make_group_compressor()
1704
 
            # Note: At this point we still have 1 copy of the fulltext (in
1705
 
            #       record and the var 'bytes'), and this generates 2 copies of
1706
 
            #       the compressed text (one for bytes, one in chunks)
1707
 
            # TODO: Push 'chunks' down into the _access api, so that we don't
1708
 
            #       have to double compressed memory here
1709
 
            # TODO: Figure out how to indicate that we would be happy to free
1710
 
            #       the fulltext content at this point. Note that sometimes we
1711
 
            #       will want it later (streaming CHK pages), but most of the
1712
 
            #       time we won't (everything else)
1713
 
            bytes = ''.join(chunks)
1714
 
            del chunks
 
1633
            bytes = self._compressor.flush().to_bytes()
1715
1634
            index, start, length = self._access.add_raw_records(
1716
1635
                [(None, len(bytes))], bytes)[0]
1717
1636
            nodes = []
1720
1639
            self._index.add_records(nodes, random_id=random_id)
1721
1640
            self._unadded_refs = {}
1722
1641
            del keys_to_add[:]
 
1642
            self._compressor = GroupCompressor()
1723
1643
 
1724
1644
        last_prefix = None
1725
1645
        max_fulltext_len = 0
1736
1656
                raise errors.RevisionNotPresent(record.key, self)
1737
1657
            if random_id:
1738
1658
                if record.key in inserted_keys:
1739
 
                    trace.note(gettext('Insert claimed random_id=True,'
1740
 
                               ' but then inserted %r two times'), record.key)
 
1659
                    trace.note('Insert claimed random_id=True,'
 
1660
                               ' but then inserted %r two times', record.key)
1741
1661
                    continue
1742
1662
                inserted_keys.add(record.key)
1743
1663
            if reuse_blocks:
1882
1802
        """See VersionedFiles.keys."""
1883
1803
        if 'evil' in debug.debug_flags:
1884
1804
            trace.mutter_callsite(2, "keys scales with size of history")
1885
 
        sources = [self._index] + self._immediate_fallback_vfs
 
1805
        sources = [self._index] + self._fallback_vfs
1886
1806
        result = set()
1887
1807
        for source in sources:
1888
1808
            result.update(source.keys())
1889
1809
        return result
1890
1810
 
1891
1811
 
1892
 
class _GCBuildDetails(object):
1893
 
    """A blob of data about the build details.
1894
 
 
1895
 
    This stores the minimal data, which then allows compatibility with the old
1896
 
    api, without taking as much memory.
1897
 
    """
1898
 
 
1899
 
    __slots__ = ('_index', '_group_start', '_group_end', '_basis_end',
1900
 
                 '_delta_end', '_parents')
1901
 
 
1902
 
    method = 'group'
1903
 
    compression_parent = None
1904
 
 
1905
 
    def __init__(self, parents, position_info):
1906
 
        self._parents = parents
1907
 
        (self._index, self._group_start, self._group_end, self._basis_end,
1908
 
         self._delta_end) = position_info
1909
 
 
1910
 
    def __repr__(self):
1911
 
        return '%s(%s, %s)' % (self.__class__.__name__,
1912
 
            self.index_memo, self._parents)
1913
 
 
1914
 
    @property
1915
 
    def index_memo(self):
1916
 
        return (self._index, self._group_start, self._group_end,
1917
 
                self._basis_end, self._delta_end)
1918
 
 
1919
 
    @property
1920
 
    def record_details(self):
1921
 
        return static_tuple.StaticTuple(self.method, None)
1922
 
 
1923
 
    def __getitem__(self, offset):
1924
 
        """Compatibility thunk to act like a tuple."""
1925
 
        if offset == 0:
1926
 
            return self.index_memo
1927
 
        elif offset == 1:
1928
 
            return self.compression_parent # Always None
1929
 
        elif offset == 2:
1930
 
            return self._parents
1931
 
        elif offset == 3:
1932
 
            return self.record_details
1933
 
        else:
1934
 
            raise IndexError('offset out of range')
1935
 
            
1936
 
    def __len__(self):
1937
 
        return 4
1938
 
 
1939
 
 
1940
1812
class _GCGraphIndex(object):
1941
1813
    """Mapper from GroupCompressVersionedFiles needs into GraphIndex storage."""
1942
1814
 
1971
1843
        # repeated over and over, this creates a surplus of ints
1972
1844
        self._int_cache = {}
1973
1845
        if track_external_parent_refs:
1974
 
            self._key_dependencies = _KeyRefs(
 
1846
            self._key_dependencies = knit._KeyRefs(
1975
1847
                track_new_keys=track_new_keys)
1976
1848
        else:
1977
1849
            self._key_dependencies = None
2117
1989
        :param keys: An iterable of keys.
2118
1990
        :return: A dict of key:
2119
1991
            (index_memo, compression_parent, parents, record_details).
2120
 
 
2121
 
            * index_memo: opaque structure to pass to read_records to extract
2122
 
              the raw data
2123
 
            * compression_parent: Content that this record is built upon, may
2124
 
              be None
2125
 
            * parents: Logical parents of this node
2126
 
            * record_details: extra information about the content which needs
2127
 
              to be passed to Factory.parse_record
 
1992
            index_memo
 
1993
                opaque structure to pass to read_records to extract the raw
 
1994
                data
 
1995
            compression_parent
 
1996
                Content that this record is built upon, may be None
 
1997
            parents
 
1998
                Logical parents of this node
 
1999
            record_details
 
2000
                extra information about the content which needs to be passed to
 
2001
                Factory.parse_record
2128
2002
        """
2129
2003
        self._check_read()
2130
2004
        result = {}
2135
2009
                parents = None
2136
2010
            else:
2137
2011
                parents = entry[3][0]
2138
 
            details = _GCBuildDetails(parents, self._node_to_position(entry))
2139
 
            result[key] = details
 
2012
            method = 'group'
 
2013
            result[key] = (self._node_to_position(entry),
 
2014
                                  None, parents, (method, None))
2140
2015
        return result
2141
2016
 
2142
2017
    def keys(self):
2158
2033
        # each, or about 7MB. Note that it might be even more when you consider
2159
2034
        # how PyInt is allocated in separate slabs. And you can't return a slab
2160
2035
        # to the OS if even 1 int on it is in use. Note though that Python uses
2161
 
        # a LIFO when re-using PyInt slots, which might cause more
 
2036
        # a LIFO when re-using PyInt slots, which probably causes more
2162
2037
        # fragmentation.
2163
2038
        start = int(bits[0])
2164
2039
        start = self._int_cache.setdefault(start, start)