/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/groupcompress.py

  • Committer: Martin
  • Date: 2017-05-25 01:35:55 UTC
  • mto: This revision was merged to the branch mainline in revision 6637.
  • Revision ID: gzlist@googlemail.com-20170525013555-lepzczdnzb9r272j
Apply 2to3 next fixer and make compatible

Show diffs side-by-side

added added

removed removed

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