~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/groupcompress.py

  • Committer: Vincent Ladeuil
  • Date: 2010-04-12 16:54:35 UTC
  • mto: (5148.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5151.
  • Revision ID: v.ladeuil+lp@free.fr-20100412165435-gzdnwuybj9rvddiz
Fix bug #519319 by defaulting to a warning for dirty trees.

* bzrlib/mutabletree.py:
(MutableTree.warn_if_changed_or_out_of_date): Factor out the
checks done by send, push and dpush.

* bzrlib/send.py:
(send): Use warn_if_changed_or_out_of_date().

* bzrlib/foreign.py:
(cmd_dpush.run): Use warn_if_changed_or_out_of_date().

* bzrlib/builtins.py:
(cmd_push.run): Use warn_if_changed_or_out_of_date().

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
23
23
except ImportError:
24
24
    pylzma = None
25
25
 
26
 
from bzrlib.lazy_import import lazy_import
27
 
lazy_import(globals(), """
28
26
from bzrlib import (
29
27
    annotate,
30
 
    config,
31
28
    debug,
32
29
    errors,
33
30
    graph as _mod_graph,
 
31
    knit,
34
32
    osutils,
35
33
    pack,
36
34
    static_tuple,
37
35
    trace,
38
 
    tsort,
39
36
    )
40
 
 
41
 
from bzrlib.repofmt import pack_repo
42
 
from bzrlib.i18n import gettext
43
 
""")
44
 
 
45
37
from bzrlib.btree_index import BTreeBuilder
46
38
from bzrlib.lru_cache import LRUSizeCache
 
39
from bzrlib.tsort import topo_sort
47
40
from bzrlib.versionedfile import (
48
 
    _KeyRefs,
49
41
    adapter_registry,
50
42
    AbsentContentFactory,
51
43
    ChunkedContentFactory,
52
44
    FulltextContentFactory,
53
 
    VersionedFilesWithFallbacks,
 
45
    VersionedFiles,
54
46
    )
55
47
 
56
48
# Minimum number of uncompressed bytes to try fetch at once when retrieving
85
77
 
86
78
    present_keys = []
87
79
    for prefix in sorted(per_prefix_map):
88
 
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
 
80
        present_keys.extend(reversed(topo_sort(per_prefix_map[prefix])))
89
81
    return present_keys
90
82
 
91
83
 
109
101
    def __init__(self):
110
102
        # map by key? or just order in file?
111
103
        self._compressor_name = None
112
 
        self._z_content_chunks = None
 
104
        self._z_content = None
113
105
        self._z_content_decompressor = None
114
106
        self._z_content_length = None
115
107
        self._content_length = None
143
135
                self._content = ''.join(self._content_chunks)
144
136
                self._content_chunks = None
145
137
        if self._content is None:
146
 
            # We join self._z_content_chunks here, because if we are
147
 
            # decompressing, then it is *very* likely that we have a single
148
 
            # chunk
149
 
            if self._z_content_chunks is None:
 
138
            if self._z_content is None:
150
139
                raise AssertionError('No content to decompress')
151
 
            z_content = ''.join(self._z_content_chunks)
152
 
            if z_content == '':
 
140
            if self._z_content == '':
153
141
                self._content = ''
154
142
            elif self._compressor_name == 'lzma':
155
143
                # We don't do partial lzma decomp yet
156
 
                self._content = pylzma.decompress(z_content)
 
144
                self._content = pylzma.decompress(self._z_content)
157
145
            elif self._compressor_name == 'zlib':
158
146
                # Start a zlib decompressor
159
147
                if num_bytes * 4 > self._content_length * 3:
160
148
                    # If we are requesting more that 3/4ths of the content,
161
149
                    # just extract the whole thing in a single pass
162
150
                    num_bytes = self._content_length
163
 
                    self._content = zlib.decompress(z_content)
 
151
                    self._content = zlib.decompress(self._z_content)
164
152
                else:
165
153
                    self._z_content_decompressor = zlib.decompressobj()
166
154
                    # Seed the decompressor with the uncompressed bytes, so
167
155
                    # that the rest of the code is simplified
168
156
                    self._content = self._z_content_decompressor.decompress(
169
 
                        z_content, num_bytes + _ZLIB_DECOMP_WINDOW)
 
157
                        self._z_content, num_bytes + _ZLIB_DECOMP_WINDOW)
170
158
                    if not self._z_content_decompressor.unconsumed_tail:
171
159
                        self._z_content_decompressor = None
172
160
            else:
219
207
            # XXX: Define some GCCorrupt error ?
220
208
            raise AssertionError('Invalid bytes: (%d) != %d + %d' %
221
209
                                 (len(bytes), pos, self._z_content_length))
222
 
        self._z_content_chunks = (bytes[pos:],)
223
 
 
224
 
    @property
225
 
    def _z_content(self):
226
 
        """Return z_content_chunks as a simple string.
227
 
 
228
 
        Meant only to be used by the test suite.
229
 
        """
230
 
        if self._z_content_chunks is not None:
231
 
            return ''.join(self._z_content_chunks)
232
 
        return None
 
210
        self._z_content = bytes[pos:]
233
211
 
234
212
    @classmethod
235
213
    def from_bytes(cls, bytes):
291
269
        self._content_length = length
292
270
        self._content_chunks = content_chunks
293
271
        self._content = None
294
 
        self._z_content_chunks = None
 
272
        self._z_content = None
295
273
 
296
274
    def set_content(self, content):
297
275
        """Set the content of this block."""
298
276
        self._content_length = len(content)
299
277
        self._content = content
300
 
        self._z_content_chunks = None
 
278
        self._z_content = None
301
279
 
302
280
    def _create_z_content_using_lzma(self):
303
281
        if self._content_chunks is not None:
305
283
            self._content_chunks = None
306
284
        if self._content is None:
307
285
            raise AssertionError('Nothing to compress')
308
 
        z_content = pylzma.compress(self._content)
309
 
        self._z_content_chunks = (z_content,)
310
 
        self._z_content_length = len(z_content)
 
286
        self._z_content = pylzma.compress(self._content)
 
287
        self._z_content_length = len(self._z_content)
311
288
 
312
 
    def _create_z_content_from_chunks(self, chunks):
 
289
    def _create_z_content_from_chunks(self):
313
290
        compressor = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION)
314
 
        # Peak in this point is 1 fulltext, 1 compressed text, + zlib overhead
315
 
        # (measured peak is maybe 30MB over the above...)
316
 
        compressed_chunks = map(compressor.compress, chunks)
 
291
        compressed_chunks = map(compressor.compress, self._content_chunks)
317
292
        compressed_chunks.append(compressor.flush())
318
 
        # Ignore empty chunks
319
 
        self._z_content_chunks = [c for c in compressed_chunks if c]
320
 
        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)
321
295
 
322
296
    def _create_z_content(self):
323
 
        if self._z_content_chunks is not None:
 
297
        if self._z_content is not None:
324
298
            return
325
299
        if _USE_LZMA:
326
300
            self._create_z_content_using_lzma()
327
301
            return
328
302
        if self._content_chunks is not None:
329
 
            chunks = self._content_chunks
330
 
        else:
331
 
            chunks = (self._content,)
332
 
        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)
333
307
 
334
 
    def to_chunks(self):
335
 
        """Create the byte stream as a series of 'chunks'"""
 
308
    def to_bytes(self):
 
309
        """Encode the information into a byte stream."""
336
310
        self._create_z_content()
337
311
        if _USE_LZMA:
338
312
            header = self.GCB_LZ_HEADER
339
313
        else:
340
314
            header = self.GCB_HEADER
341
 
        chunks = ['%s%d\n%d\n'
342
 
                  % (header, self._z_content_length, self._content_length),
 
315
        chunks = [header,
 
316
                  '%d\n%d\n' % (self._z_content_length, self._content_length),
 
317
                  self._z_content,
343
318
                 ]
344
 
        chunks.extend(self._z_content_chunks)
345
 
        total_len = sum(map(len, chunks))
346
 
        return total_len, chunks
347
 
 
348
 
    def to_bytes(self):
349
 
        """Encode the information into a byte stream."""
350
 
        total_len, chunks = self.to_chunks()
351
319
        return ''.join(chunks)
352
320
 
353
321
    def _dump(self, include_text=False):
467
435
                # Grab and cache the raw bytes for this entry
468
436
                # and break the ref-cycle with _manager since we don't need it
469
437
                # anymore
470
 
                try:
471
 
                    self._manager._prepare_for_extract()
472
 
                except zlib.error as value:
473
 
                    raise errors.DecompressCorruption("zlib: " + str(value))
 
438
                self._manager._prepare_for_extract()
474
439
                block = self._manager._block
475
440
                self._bytes = block.extract(self.key, self._start, self._end)
476
441
                # There are code paths that first extract as fulltext, and then
495
460
    _full_enough_block_size = 3*1024*1024 # size at which we won't repack
496
461
    _full_enough_mixed_block_size = 2*768*1024 # 1.5MB
497
462
 
498
 
    def __init__(self, block, get_compressor_settings=None):
 
463
    def __init__(self, block):
499
464
        self._block = block
500
465
        # We need to preserve the ordering
501
466
        self._factories = []
502
467
        self._last_byte = 0
503
 
        self._get_settings = get_compressor_settings
504
 
        self._compressor_settings = None
505
 
 
506
 
    def _get_compressor_settings(self):
507
 
        if self._compressor_settings is not None:
508
 
            return self._compressor_settings
509
 
        settings = None
510
 
        if self._get_settings is not None:
511
 
            settings = self._get_settings()
512
 
        if settings is None:
513
 
            vf = GroupCompressVersionedFiles
514
 
            settings = vf._DEFAULT_COMPRESSOR_SETTINGS
515
 
        self._compressor_settings = settings
516
 
        return self._compressor_settings
517
468
 
518
469
    def add_factory(self, key, parents, start, end):
519
470
        if not self._factories:
552
503
        new_block.set_content(self._block._content[:last_byte])
553
504
        self._block = new_block
554
505
 
555
 
    def _make_group_compressor(self):
556
 
        return GroupCompressor(self._get_compressor_settings())
557
 
 
558
506
    def _rebuild_block(self):
559
507
        """Create a new GroupCompressBlock with only the referenced texts."""
560
 
        compressor = self._make_group_compressor()
 
508
        compressor = GroupCompressor()
561
509
        tstart = time.time()
562
510
        old_length = self._block._content_length
563
511
        end_point = 0
575
523
        #       block? It seems hard to come up with a method that it would
576
524
        #       expand, since we do full compression again. Perhaps based on a
577
525
        #       request that ends up poorly ordered?
578
 
        # TODO: If the content would have expanded, then we would want to
579
 
        #       handle a case where we need to split the block.
580
 
        #       Now that we have a user-tweakable option
581
 
        #       (max_bytes_to_index), it is possible that one person set it
582
 
        #       to a very low value, causing poor compression.
583
526
        delta = time.time() - tstart
584
527
        self._block = new_block
585
528
        trace.mutter('creating new compressed block on-the-fly in %.3fs'
736
679
        z_header_bytes = zlib.compress(header_bytes)
737
680
        del header_bytes
738
681
        z_header_bytes_len = len(z_header_bytes)
739
 
        block_bytes_len, block_chunks = self._block.to_chunks()
 
682
        block_bytes = self._block.to_bytes()
740
683
        lines.append('%d\n%d\n%d\n' % (z_header_bytes_len, header_bytes_len,
741
 
                                       block_bytes_len))
 
684
                                       len(block_bytes)))
742
685
        lines.append(z_header_bytes)
743
 
        lines.extend(block_chunks)
744
 
        del z_header_bytes, block_chunks
745
 
        # TODO: This is a point where we will double the memory consumption. To
746
 
        #       avoid this, we probably have to switch to a 'chunked' api
 
686
        lines.append(block_bytes)
 
687
        del z_header_bytes, block_bytes
747
688
        return ''.join(lines)
748
689
 
749
690
    @classmethod
750
691
    def from_bytes(cls, bytes):
751
692
        # TODO: This does extra string copying, probably better to do it a
752
 
        #       different way. At a minimum this creates 2 copies of the
753
 
        #       compressed content
 
693
        #       different way
754
694
        (storage_kind, z_header_len, header_len,
755
695
         block_len, rest) = bytes.split('\n', 4)
756
696
        del bytes
808
748
 
809
749
class _CommonGroupCompressor(object):
810
750
 
811
 
    def __init__(self, settings=None):
 
751
    def __init__(self):
812
752
        """Create a GroupCompressor."""
813
753
        self.chunks = []
814
754
        self._last = None
817
757
        self.labels_deltas = {}
818
758
        self._delta_index = None # Set by the children
819
759
        self._block = GroupCompressBlock()
820
 
        if settings is None:
821
 
            self._settings = {}
822
 
        else:
823
 
            self._settings = settings
824
760
 
825
761
    def compress(self, key, bytes, expected_sha, nostore_sha=None, soft=False):
826
762
        """Compress lines with label key.
918
854
 
919
855
        After calling this, the compressor should no longer be used
920
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.
921
865
        self._block.set_chunked_content(self.chunks, self.endpoint)
922
866
        self.chunks = None
923
867
        self._delta_index = None
941
885
 
942
886
class PythonGroupCompressor(_CommonGroupCompressor):
943
887
 
944
 
    def __init__(self, settings=None):
 
888
    def __init__(self):
945
889
        """Create a GroupCompressor.
946
890
 
947
891
        Used only if the pyrex version is not available.
948
892
        """
949
 
        super(PythonGroupCompressor, self).__init__(settings)
 
893
        super(PythonGroupCompressor, self).__init__()
950
894
        self._delta_index = LinesDeltaIndex([])
951
895
        # The actual content is managed by LinesDeltaIndex
952
896
        self.chunks = self._delta_index.lines
989
933
 
990
934
    It contains code very similar to SequenceMatcher because of having a similar
991
935
    task. However some key differences apply:
992
 
 
993
 
    * there is no junk, we want a minimal edit not a human readable diff.
994
 
    * we don't filter very common lines (because we don't know where a good
995
 
      range will start, and after the first text we want to be emitting minmal
996
 
      edits only.
997
 
    * we chain the left side, not the right side
998
 
    * we incrementally update the adjacency matrix as new lines are provided.
999
 
    * we look for matches in all of the left side, so the routine which does
1000
 
      the analagous task of find_longest_match does not need to filter on the
1001
 
      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.
1002
945
    """
1003
946
 
1004
 
    def __init__(self, settings=None):
1005
 
        super(PyrexGroupCompressor, self).__init__(settings)
1006
 
        max_bytes_to_index = self._settings.get('max_bytes_to_index', 0)
1007
 
        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()
1008
950
 
1009
951
    def _compress(self, key, bytes, max_delta_size, soft=False):
1010
952
        """see _CommonGroupCompressor._compress"""
1085
1027
        index = _GCGraphIndex(graph_index, lambda:True, parents=parents,
1086
1028
            add_callback=graph_index.add_nodes,
1087
1029
            inconsistency_fatal=inconsistency_fatal)
1088
 
        access = pack_repo._DirectPackAccess({})
 
1030
        access = knit._DirectPackAccess({})
1089
1031
        access.set_writer(writer, graph_index, (transport, 'newpack'))
1090
1032
        result = GroupCompressVersionedFiles(index, access, delta)
1091
1033
        result.stream = stream
1101
1043
 
1102
1044
class _BatchingBlockFetcher(object):
1103
1045
    """Fetch group compress blocks in batches.
1104
 
 
 
1046
    
1105
1047
    :ivar total_bytes: int of expected number of bytes needed to fetch the
1106
1048
        currently pending batch.
1107
1049
    """
1108
1050
 
1109
 
    def __init__(self, gcvf, locations, get_compressor_settings=None):
 
1051
    def __init__(self, gcvf, locations):
1110
1052
        self.gcvf = gcvf
1111
1053
        self.locations = locations
1112
1054
        self.keys = []
1115
1057
        self.total_bytes = 0
1116
1058
        self.last_read_memo = None
1117
1059
        self.manager = None
1118
 
        self._get_compressor_settings = get_compressor_settings
1119
1060
 
1120
1061
    def add_key(self, key):
1121
1062
        """Add another to key to fetch.
1122
 
 
 
1063
        
1123
1064
        :return: The estimated number of bytes needed to fetch the batch so
1124
1065
            far.
1125
1066
        """
1150
1091
            # and then.
1151
1092
            self.batch_memos[read_memo] = cached_block
1152
1093
        return self.total_bytes
1153
 
 
 
1094
        
1154
1095
    def _flush_manager(self):
1155
1096
        if self.manager is not None:
1156
1097
            for factory in self.manager.get_record_stream():
1161
1102
    def yield_factories(self, full_flush=False):
1162
1103
        """Yield factories for keys added since the last yield.  They will be
1163
1104
        returned in the order they were added via add_key.
1164
 
 
 
1105
        
1165
1106
        :param full_flush: by default, some results may not be returned in case
1166
1107
            they can be part of the next batch.  If full_flush is True, then
1167
1108
            all results are returned.
1195
1136
                    memos_to_get_stack.pop()
1196
1137
                else:
1197
1138
                    block = self.batch_memos[read_memo]
1198
 
                self.manager = _LazyGroupContentManager(block,
1199
 
                    get_compressor_settings=self._get_compressor_settings)
 
1139
                self.manager = _LazyGroupContentManager(block)
1200
1140
                self.last_read_memo = read_memo
1201
1141
            start, end = index_memo[3:5]
1202
1142
            self.manager.add_factory(key, parents, start, end)
1209
1149
        self.total_bytes = 0
1210
1150
 
1211
1151
 
1212
 
class GroupCompressVersionedFiles(VersionedFilesWithFallbacks):
 
1152
class GroupCompressVersionedFiles(VersionedFiles):
1213
1153
    """A group-compress based VersionedFiles implementation."""
1214
1154
 
1215
 
    # This controls how the GroupCompress DeltaIndex works. Basically, we
1216
 
    # compute hash pointers into the source blocks (so hash(text) => text).
1217
 
    # However each of these references costs some memory in trade against a
1218
 
    # more accurate match result. For very large files, they either are
1219
 
    # pre-compressed and change in bulk whenever they change, or change in just
1220
 
    # local blocks. Either way, 'improved resolution' is not very helpful,
1221
 
    # versus running out of memory trying to track everything. The default max
1222
 
    # gives 100% sampling of a 1MB file.
1223
 
    _DEFAULT_MAX_BYTES_TO_INDEX = 1024 * 1024
1224
 
    _DEFAULT_COMPRESSOR_SETTINGS = {'max_bytes_to_index':
1225
 
                                     _DEFAULT_MAX_BYTES_TO_INDEX}
1226
 
 
1227
 
    def __init__(self, index, access, delta=True, _unadded_refs=None,
1228
 
                 _group_cache=None):
 
1155
    def __init__(self, index, access, delta=True, _unadded_refs=None):
1229
1156
        """Create a GroupCompressVersionedFiles object.
1230
1157
 
1231
1158
        :param index: The index object storing access and graph data.
1232
1159
        :param access: The access object storing raw data.
1233
1160
        :param delta: Whether to delta compress or just entropy compress.
1234
1161
        :param _unadded_refs: private parameter, don't use.
1235
 
        :param _group_cache: private parameter, don't use.
1236
1162
        """
1237
1163
        self._index = index
1238
1164
        self._access = access
1240
1166
        if _unadded_refs is None:
1241
1167
            _unadded_refs = {}
1242
1168
        self._unadded_refs = _unadded_refs
1243
 
        if _group_cache is None:
1244
 
            _group_cache = LRUSizeCache(max_size=50*1024*1024)
1245
 
        self._group_cache = _group_cache
1246
 
        self._immediate_fallback_vfs = []
1247
 
        self._max_bytes_to_index = None
 
1169
        self._group_cache = LRUSizeCache(max_size=50*1024*1024)
 
1170
        self._fallback_vfs = []
1248
1171
 
1249
1172
    def without_fallbacks(self):
1250
1173
        """Return a clone of this object without any fallbacks configured."""
1251
1174
        return GroupCompressVersionedFiles(self._index, self._access,
1252
 
            self._delta, _unadded_refs=dict(self._unadded_refs),
1253
 
            _group_cache=self._group_cache)
 
1175
            self._delta, _unadded_refs=dict(self._unadded_refs))
1254
1176
 
1255
1177
    def add_lines(self, key, parents, lines, parent_texts=None,
1256
1178
        left_matching_blocks=None, nostore_sha=None, random_id=False,
1260
1182
        :param key: The key tuple of the text to add.
1261
1183
        :param parents: The parents key tuples of the text to add.
1262
1184
        :param lines: A list of lines. Each line must be a bytestring. And all
1263
 
            of them except the last must be terminated with \\n and contain no
1264
 
            other \\n's. The last line may either contain no \\n's or a single
1265
 
            terminating \\n. If the lines list does meet this constraint the
1266
 
            add routine may error or may succeed - but you will be unable to
1267
 
            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
1268
1190
            correctly is expensive and extremely unlikely to catch bugs so it
1269
1191
            is not done at runtime unless check_content is True.)
1270
1192
        :param parent_texts: An optional dictionary containing the opaque
1325
1247
 
1326
1248
        :param a_versioned_files: A VersionedFiles object.
1327
1249
        """
1328
 
        self._immediate_fallback_vfs.append(a_versioned_files)
 
1250
        self._fallback_vfs.append(a_versioned_files)
1329
1251
 
1330
1252
    def annotate(self, key):
1331
1253
        """See VersionedFiles.annotate."""
1365
1287
            self._check_lines_not_unicode(lines)
1366
1288
            self._check_lines_are_lines(lines)
1367
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
 
1368
1306
    def get_parent_map(self, keys):
1369
1307
        """Get a map of the graph parents of keys.
1370
1308
 
1385
1323
            and so on.
1386
1324
        """
1387
1325
        result = {}
1388
 
        sources = [self._index] + self._immediate_fallback_vfs
 
1326
        sources = [self._index] + self._fallback_vfs
1389
1327
        source_results = []
1390
1328
        missing = set(keys)
1391
1329
        for source in sources:
1492
1430
        parent_map = {}
1493
1431
        key_to_source_map = {}
1494
1432
        source_results = []
1495
 
        for source in self._immediate_fallback_vfs:
 
1433
        for source in self._fallback_vfs:
1496
1434
            if not missing:
1497
1435
                break
1498
1436
            source_parents = source.get_parent_map(missing)
1508
1446
 
1509
1447
        The returned objects should be in the order defined by 'ordering',
1510
1448
        which can weave between different sources.
1511
 
 
1512
1449
        :param ordering: Must be one of 'topological' or 'groupcompress'
1513
1450
        :return: List of [(source, [keys])] tuples, such that all keys are in
1514
1451
            the defined order, regardless of source.
1515
1452
        """
1516
1453
        if ordering == 'topological':
1517
 
            present_keys = tsort.topo_sort(parent_map)
 
1454
            present_keys = topo_sort(parent_map)
1518
1455
        else:
1519
1456
            # ordering == 'groupcompress'
1520
1457
            # XXX: This only optimizes for the target ordering. We may need
1609
1546
        #  - we encounter an unadded ref, or
1610
1547
        #  - we run out of keys, or
1611
1548
        #  - the total bytes to retrieve for this batch > BATCH_SIZE
1612
 
        batcher = _BatchingBlockFetcher(self, locations,
1613
 
            get_compressor_settings=self._get_compressor_settings)
 
1549
        batcher = _BatchingBlockFetcher(self, locations)
1614
1550
        for source, keys in source_keys:
1615
1551
            if source is self:
1616
1552
                for key in keys:
1662
1598
        for _ in self._insert_record_stream(stream, random_id=False):
1663
1599
            pass
1664
1600
 
1665
 
    def _get_compressor_settings(self):
1666
 
        if self._max_bytes_to_index is None:
1667
 
            # TODO: VersionedFiles don't know about their containing
1668
 
            #       repository, so they don't have much of an idea about their
1669
 
            #       location. So for now, this is only a global option.
1670
 
            c = config.GlobalConfig()
1671
 
            val = c.get_user_option('bzr.groupcompress.max_bytes_to_index')
1672
 
            if val is not None:
1673
 
                try:
1674
 
                    val = int(val)
1675
 
                except ValueError, e:
1676
 
                    trace.warning('Value for '
1677
 
                                  '"bzr.groupcompress.max_bytes_to_index"'
1678
 
                                  ' %r is not an integer'
1679
 
                                  % (val,))
1680
 
                    val = None
1681
 
            if val is None:
1682
 
                val = self._DEFAULT_MAX_BYTES_TO_INDEX
1683
 
            self._max_bytes_to_index = val
1684
 
        return {'max_bytes_to_index': self._max_bytes_to_index}
1685
 
 
1686
 
    def _make_group_compressor(self):
1687
 
        return GroupCompressor(self._get_compressor_settings())
1688
 
 
1689
1601
    def _insert_record_stream(self, stream, random_id=False, nostore_sha=None,
1690
1602
                              reuse_blocks=True):
1691
1603
        """Internal core to insert a record stream into this container.
1714
1626
                return adapter
1715
1627
        # This will go up to fulltexts for gc to gc fetching, which isn't
1716
1628
        # ideal.
1717
 
        self._compressor = self._make_group_compressor()
 
1629
        self._compressor = GroupCompressor()
1718
1630
        self._unadded_refs = {}
1719
1631
        keys_to_add = []
1720
1632
        def flush():
1721
 
            bytes_len, chunks = self._compressor.flush().to_chunks()
1722
 
            self._compressor = self._make_group_compressor()
1723
 
            # Note: At this point we still have 1 copy of the fulltext (in
1724
 
            #       record and the var 'bytes'), and this generates 2 copies of
1725
 
            #       the compressed text (one for bytes, one in chunks)
1726
 
            # TODO: Push 'chunks' down into the _access api, so that we don't
1727
 
            #       have to double compressed memory here
1728
 
            # TODO: Figure out how to indicate that we would be happy to free
1729
 
            #       the fulltext content at this point. Note that sometimes we
1730
 
            #       will want it later (streaming CHK pages), but most of the
1731
 
            #       time we won't (everything else)
1732
 
            bytes = ''.join(chunks)
1733
 
            del chunks
 
1633
            bytes = self._compressor.flush().to_bytes()
1734
1634
            index, start, length = self._access.add_raw_records(
1735
1635
                [(None, len(bytes))], bytes)[0]
1736
1636
            nodes = []
1739
1639
            self._index.add_records(nodes, random_id=random_id)
1740
1640
            self._unadded_refs = {}
1741
1641
            del keys_to_add[:]
 
1642
            self._compressor = GroupCompressor()
1742
1643
 
1743
1644
        last_prefix = None
1744
1645
        max_fulltext_len = 0
1755
1656
                raise errors.RevisionNotPresent(record.key, self)
1756
1657
            if random_id:
1757
1658
                if record.key in inserted_keys:
1758
 
                    trace.note(gettext('Insert claimed random_id=True,'
1759
 
                               ' 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)
1760
1661
                    continue
1761
1662
                inserted_keys.add(record.key)
1762
1663
            if reuse_blocks:
1901
1802
        """See VersionedFiles.keys."""
1902
1803
        if 'evil' in debug.debug_flags:
1903
1804
            trace.mutter_callsite(2, "keys scales with size of history")
1904
 
        sources = [self._index] + self._immediate_fallback_vfs
 
1805
        sources = [self._index] + self._fallback_vfs
1905
1806
        result = set()
1906
1807
        for source in sources:
1907
1808
            result.update(source.keys())
1908
1809
        return result
1909
1810
 
1910
1811
 
1911
 
class _GCBuildDetails(object):
1912
 
    """A blob of data about the build details.
1913
 
 
1914
 
    This stores the minimal data, which then allows compatibility with the old
1915
 
    api, without taking as much memory.
1916
 
    """
1917
 
 
1918
 
    __slots__ = ('_index', '_group_start', '_group_end', '_basis_end',
1919
 
                 '_delta_end', '_parents')
1920
 
 
1921
 
    method = 'group'
1922
 
    compression_parent = None
1923
 
 
1924
 
    def __init__(self, parents, position_info):
1925
 
        self._parents = parents
1926
 
        (self._index, self._group_start, self._group_end, self._basis_end,
1927
 
         self._delta_end) = position_info
1928
 
 
1929
 
    def __repr__(self):
1930
 
        return '%s(%s, %s)' % (self.__class__.__name__,
1931
 
            self.index_memo, self._parents)
1932
 
 
1933
 
    @property
1934
 
    def index_memo(self):
1935
 
        return (self._index, self._group_start, self._group_end,
1936
 
                self._basis_end, self._delta_end)
1937
 
 
1938
 
    @property
1939
 
    def record_details(self):
1940
 
        return static_tuple.StaticTuple(self.method, None)
1941
 
 
1942
 
    def __getitem__(self, offset):
1943
 
        """Compatibility thunk to act like a tuple."""
1944
 
        if offset == 0:
1945
 
            return self.index_memo
1946
 
        elif offset == 1:
1947
 
            return self.compression_parent # Always None
1948
 
        elif offset == 2:
1949
 
            return self._parents
1950
 
        elif offset == 3:
1951
 
            return self.record_details
1952
 
        else:
1953
 
            raise IndexError('offset out of range')
1954
 
            
1955
 
    def __len__(self):
1956
 
        return 4
1957
 
 
1958
 
 
1959
1812
class _GCGraphIndex(object):
1960
1813
    """Mapper from GroupCompressVersionedFiles needs into GraphIndex storage."""
1961
1814
 
1990
1843
        # repeated over and over, this creates a surplus of ints
1991
1844
        self._int_cache = {}
1992
1845
        if track_external_parent_refs:
1993
 
            self._key_dependencies = _KeyRefs(
 
1846
            self._key_dependencies = knit._KeyRefs(
1994
1847
                track_new_keys=track_new_keys)
1995
1848
        else:
1996
1849
            self._key_dependencies = None
2136
1989
        :param keys: An iterable of keys.
2137
1990
        :return: A dict of key:
2138
1991
            (index_memo, compression_parent, parents, record_details).
2139
 
 
2140
 
            * index_memo: opaque structure to pass to read_records to extract
2141
 
              the raw data
2142
 
            * compression_parent: Content that this record is built upon, may
2143
 
              be None
2144
 
            * parents: Logical parents of this node
2145
 
            * record_details: extra information about the content which needs
2146
 
              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
2147
2002
        """
2148
2003
        self._check_read()
2149
2004
        result = {}
2154
2009
                parents = None
2155
2010
            else:
2156
2011
                parents = entry[3][0]
2157
 
            details = _GCBuildDetails(parents, self._node_to_position(entry))
2158
 
            result[key] = details
 
2012
            method = 'group'
 
2013
            result[key] = (self._node_to_position(entry),
 
2014
                                  None, parents, (method, None))
2159
2015
        return result
2160
2016
 
2161
2017
    def keys(self):
2177
2033
        # each, or about 7MB. Note that it might be even more when you consider
2178
2034
        # how PyInt is allocated in separate slabs. And you can't return a slab
2179
2035
        # to the OS if even 1 int on it is in use. Note though that Python uses
2180
 
        # a LIFO when re-using PyInt slots, which might cause more
 
2036
        # a LIFO when re-using PyInt slots, which probably causes more
2181
2037
        # fragmentation.
2182
2038
        start = int(bits[0])
2183
2039
        start = self._int_cache.setdefault(start, start)