23
23
except ImportError:
26
from bzrlib.lazy_import import lazy_import
27
lazy_import(globals(), """
28
26
from bzrlib import (
33
30
graph as _mod_graph,
41
from bzrlib.repofmt import pack_repo
42
from bzrlib.i18n import gettext
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 (
50
42
AbsentContentFactory,
51
43
ChunkedContentFactory,
52
44
FulltextContentFactory,
53
VersionedFilesWithFallbacks,
56
48
# Minimum number of uncompressed bytes to try fetch at once when retrieving
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
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)
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)
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
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:],)
225
def _z_content(self):
226
"""Return z_content_chunks as a simple string.
228
Meant only to be used by the test suite.
230
if self._z_content_chunks is not None:
231
return ''.join(self._z_content_chunks)
210
self._z_content = bytes[pos:]
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
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
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)
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)
322
296
def _create_z_content(self):
323
if self._z_content_chunks is not None:
297
if self._z_content is not None:
326
300
self._create_z_content_using_lzma()
328
302
if self._content_chunks is not None:
329
chunks = self._content_chunks
331
chunks = (self._content,)
332
self._create_z_content_from_chunks(chunks)
303
self._create_z_content_from_chunks()
305
self._z_content = zlib.compress(self._content)
306
self._z_content_length = len(self._z_content)
335
"""Create the byte stream as a series of 'chunks'"""
309
"""Encode the information into a byte stream."""
336
310
self._create_z_content()
338
312
header = self.GCB_LZ_HEADER
340
314
header = self.GCB_HEADER
341
chunks = ['%s%d\n%d\n'
342
% (header, self._z_content_length, self._content_length),
316
'%d\n%d\n' % (self._z_content_length, self._content_length),
344
chunks.extend(self._z_content_chunks)
345
total_len = sum(map(len, chunks))
346
return total_len, chunks
349
"""Encode the information into a byte stream."""
350
total_len, chunks = self.to_chunks()
351
319
return ''.join(chunks)
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
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
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
506
def _get_compressor_settings(self):
507
if self._compressor_settings is not None:
508
return self._compressor_settings
510
if self._get_settings is not None:
511
settings = self._get_settings()
513
vf = GroupCompressVersionedFiles
514
settings = vf._DEFAULT_COMPRESSOR_SETTINGS
515
self._compressor_settings = settings
516
return self._compressor_settings
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
555
def _make_group_compressor(self):
556
return GroupCompressor(self._get_compressor_settings())
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
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)
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,
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)
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
754
694
(storage_kind, z_header_len, header_len,
755
695
block_len, rest) = bytes.split('\n', 4)
817
757
self.labels_deltas = {}
818
758
self._delta_index = None # Set by the children
819
759
self._block = GroupCompressBlock()
823
self._settings = settings
825
761
def compress(self, key, bytes, expected_sha, nostore_sha=None, soft=False):
826
762
"""Compress lines with label key.
919
855
After calling this, the compressor should no longer be used
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
942
886
class PythonGroupCompressor(_CommonGroupCompressor):
944
def __init__(self, settings=None):
945
889
"""Create a GroupCompressor.
947
891
Used only if the pyrex version is not available.
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
990
934
It contains code very similar to SequenceMatcher because of having a similar
991
935
task. However some key differences apply:
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
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
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
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
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)
948
super(PyrexGroupCompressor, self).__init__()
949
self._delta_index = DeltaIndex()
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
1102
1044
class _BatchingBlockFetcher(object):
1103
1045
"""Fetch group compress blocks in batches.
1105
1047
:ivar total_bytes: int of expected number of bytes needed to fetch the
1106
1048
currently pending batch.
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
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.
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()
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
1212
class GroupCompressVersionedFiles(VersionedFilesWithFallbacks):
1152
class GroupCompressVersionedFiles(VersionedFiles):
1213
1153
"""A group-compress based VersionedFiles implementation."""
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}
1227
def __init__(self, index, access, delta=True, _unadded_refs=None,
1155
def __init__(self, index, access, delta=True, _unadded_refs=None):
1229
1156
"""Create a GroupCompressVersionedFiles object.
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.
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 = []
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))
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
1365
1287
self._check_lines_not_unicode(lines)
1366
1288
self._check_lines_are_lines(lines)
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
1295
parent_map, missing_keys = self._index.find_ancestry(keys)
1296
for fallback in self._fallback_vfs:
1297
if not missing_keys:
1299
(f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
1301
parent_map.update(f_parent_map)
1302
missing_keys = f_missing_keys
1303
kg = _mod_graph.KnownGraph(parent_map)
1368
1306
def get_parent_map(self, keys):
1369
1307
"""Get a map of the graph parents of keys.
1509
1447
The returned objects should be in the order defined by 'ordering',
1510
1448
which can weave between different sources.
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.
1516
1453
if ordering == 'topological':
1517
present_keys = tsort.topo_sort(parent_map)
1454
present_keys = topo_sort(parent_map)
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):
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')
1675
except ValueError, e:
1676
trace.warning('Value for '
1677
'"bzr.groupcompress.max_bytes_to_index"'
1678
' %r is not an integer'
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}
1686
def _make_group_compressor(self):
1687
return GroupCompressor(self._get_compressor_settings())
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.
1715
1627
# This will go up to fulltexts for gc to gc fetching, which isn't
1717
self._compressor = self._make_group_compressor()
1629
self._compressor = GroupCompressor()
1718
1630
self._unadded_refs = {}
1719
1631
keys_to_add = []
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)
1633
bytes = self._compressor.flush().to_bytes()
1734
1634
index, start, length = self._access.add_raw_records(
1735
1635
[(None, len(bytes))], bytes)[0]
1755
1656
raise errors.RevisionNotPresent(record.key, self)
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)
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
1906
1807
for source in sources:
1907
1808
result.update(source.keys())
1911
class _GCBuildDetails(object):
1912
"""A blob of data about the build details.
1914
This stores the minimal data, which then allows compatibility with the old
1915
api, without taking as much memory.
1918
__slots__ = ('_index', '_group_start', '_group_end', '_basis_end',
1919
'_delta_end', '_parents')
1922
compression_parent = None
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
1930
return '%s(%s, %s)' % (self.__class__.__name__,
1931
self.index_memo, self._parents)
1934
def index_memo(self):
1935
return (self._index, self._group_start, self._group_end,
1936
self._basis_end, self._delta_end)
1939
def record_details(self):
1940
return static_tuple.StaticTuple(self.method, None)
1942
def __getitem__(self, offset):
1943
"""Compatibility thunk to act like a tuple."""
1945
return self.index_memo
1947
return self.compression_parent # Always None
1949
return self._parents
1951
return self.record_details
1953
raise IndexError('offset out of range')
1959
1812
class _GCGraphIndex(object):
1960
1813
"""Mapper from GroupCompressVersionedFiles needs into GraphIndex storage."""
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)
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).
2140
* index_memo: opaque structure to pass to read_records to extract
2142
* compression_parent: Content that this record is built upon, may
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
1993
opaque structure to pass to read_records to extract the raw
1996
Content that this record is built upon, may be None
1998
Logical parents of this node
2000
extra information about the content which needs to be passed to
2001
Factory.parse_record
2148
2003
self._check_read()
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)