~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/groupcompress.py

  • Committer: John Arbash Meinel
  • Date: 2010-02-10 17:52:08 UTC
  • mfrom: (5021 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5023.
  • Revision ID: john@arbash-meinel.com-20100210175208-bubuwav4uqigu291
Merge bzr.dev 5021 to resolve NEWS

Show diffs side-by-side

added added

removed removed

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