58
56
adapter_registry = Registry()
57
adapter_registry.register_lazy(('knit-delta-gz', 'fulltext'), 'breezy.bzr.knit',
58
'DeltaPlainToFullText')
59
adapter_registry.register_lazy(('knit-ft-gz', 'fulltext'), 'breezy.bzr.knit',
59
61
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'knit-delta-gz'),
60
62
'breezy.bzr.knit', 'DeltaAnnotatedToUnannotated')
63
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'fulltext'),
64
'breezy.bzr.knit', 'DeltaAnnotatedToFullText')
61
65
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'knit-ft-gz'),
62
66
'breezy.bzr.knit', 'FTAnnotatedToUnannotated')
63
for target_storage_kind in ('fulltext', 'chunked', 'lines'):
64
adapter_registry.register_lazy(('knit-delta-gz', target_storage_kind), 'breezy.bzr.knit',
65
'DeltaPlainToFullText')
66
adapter_registry.register_lazy(('knit-ft-gz', target_storage_kind), 'breezy.bzr.knit',
68
adapter_registry.register_lazy(('knit-annotated-ft-gz', target_storage_kind),
69
'breezy.bzr.knit', 'FTAnnotatedToFullText')
70
adapter_registry.register_lazy(('knit-annotated-delta-gz', target_storage_kind),
71
'breezy.bzr.knit', 'DeltaAnnotatedToFullText')
67
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'fulltext'),
68
'breezy.bzr.knit', 'FTAnnotatedToFullText')
69
# adapter_registry.register_lazy(('knit-annotated-ft-gz', 'chunked'),
70
# 'breezy.bzr.knit', 'FTAnnotatedToChunked')
74
73
class ContentFactory(object):
75
74
"""Abstract interface for insertion and retrieval from a VersionedFile.
77
76
:ivar sha1: None, or the sha1 of the content fulltext.
78
:ivar size: None, or the size of the content fulltext.
79
77
:ivar storage_kind: The native storage kind of this factory. One of
80
78
'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
81
79
'knit-delta', 'fulltext', 'knit-annotated-ft-gz',
104
101
satisfies this, as does a list of lines.
106
103
:ivar sha1: None, or the sha1 of the content fulltext.
107
:ivar size: None, or the size of the content fulltext.
108
104
:ivar storage_kind: The native storage kind of this factory. Always
110
106
:ivar key: The key of this content. Each key is a tuple with a single
112
108
:ivar parents: A tuple of parent keys for self.key. If the object has
113
109
no parent information, None (as opposed to () for an empty list of
115
:ivar chunks_are_lines: Whether chunks are lines.
118
def __init__(self, key, parents, sha1, chunks, chunks_are_lines=None):
113
def __init__(self, key, parents, sha1, chunks):
119
114
"""Create a ContentFactory."""
121
self.size = sum(map(len, chunks))
122
116
self.storage_kind = 'chunked'
124
118
self.parents = parents
125
119
self._chunks = chunks
126
self._chunks_are_lines = chunks_are_lines
128
121
def get_bytes_as(self, storage_kind):
129
122
if storage_kind == 'chunked':
130
123
return self._chunks
131
124
elif storage_kind == 'fulltext':
132
125
return b''.join(self._chunks)
133
elif storage_kind == 'lines':
134
if self._chunks_are_lines:
136
return list(osutils.chunks_to_lines(self._chunks))
137
126
raise errors.UnavailableRepresentation(self.key, storage_kind,
138
127
self.storage_kind)
140
def iter_bytes_as(self, storage_kind):
141
if storage_kind == 'chunked':
142
return iter(self._chunks)
143
elif storage_kind == 'lines':
144
if self._chunks_are_lines:
145
return iter(self._chunks)
146
return iter(osutils.chunks_to_lines(self._chunks))
147
raise errors.UnavailableRepresentation(self.key, storage_kind,
150
130
class FulltextContentFactory(ContentFactory):
151
131
"""Static data content factory.
179
158
return self._text
180
159
elif storage_kind == 'chunked':
181
160
return [self._text]
182
elif storage_kind == 'lines':
183
return osutils.split_lines(self._text)
184
raise errors.UnavailableRepresentation(self.key, storage_kind,
187
def iter_bytes_as(self, storage_kind):
188
if storage_kind == 'chunked':
189
return iter([self._text])
190
elif storage_kind == 'lines':
191
return iter(osutils.split_lines(self._text))
192
raise errors.UnavailableRepresentation(self.key, storage_kind,
196
class FileContentFactory(ContentFactory):
197
"""File-based content factory.
200
def __init__(self, key, parents, fileobj, sha1=None, size=None):
202
self.parents = parents
204
self.storage_kind = 'file'
208
def get_bytes_as(self, storage_kind):
210
if storage_kind == 'fulltext':
211
return self.file.read()
212
elif storage_kind == 'chunked':
213
return list(osutils.file_iterator(self.file))
214
elif storage_kind == 'lines':
215
return list(self.file.readlines())
216
raise errors.UnavailableRepresentation(self.key, storage_kind,
219
def iter_bytes_as(self, storage_kind):
221
if storage_kind == 'chunked':
222
return osutils.file_iterator(self.file)
223
elif storage_kind == 'lines':
225
161
raise errors.UnavailableRepresentation(self.key, storage_kind,
226
162
self.storage_kind)
250
185
' code does not handle if it is missing.'
253
def iter_bytes_as(self, storage_kind):
254
raise ValueError('A request was made for key: %s, but that'
255
' content is not available, and the calling'
256
' code does not handle if it is missing.'
260
189
class AdapterFactory(ContentFactory):
261
190
"""A content factory to adapt between key prefix's."""
794
723
return self._backing_vf.add_lines(key, parents, lines, parent_texts,
795
724
left_matching_blocks, nostore_sha, random_id, check_content)
797
def add_content(self, factory, parent_texts=None,
798
left_matching_blocks=None, nostore_sha=None, random_id=False,
800
self.calls.append(("add_content", factory, parent_texts,
801
left_matching_blocks, nostore_sha, random_id, check_content))
802
return self._backing_vf.add_content(
803
factory, parent_texts, left_matching_blocks, nostore_sha,
804
random_id, check_content)
807
727
self._backing_vf.check()
1059
979
raise NotImplementedError(self.add_lines)
1061
def add_content(self, factory, parent_texts=None,
1062
left_matching_blocks=None, nostore_sha=None, random_id=False,
1063
check_content=True):
1064
"""Add a text to the store from a chunk iterable.
1066
:param key: The key tuple of the text to add. If the last element is
1067
None, a CHK string will be generated during the addition.
1068
:param parents: The parents key tuples of the text to add.
1069
:param chunk_iter: An iterable over bytestrings.
1070
:param parent_texts: An optional dictionary containing the opaque
1071
representations of some or all of the parents of version_id to
1072
allow delta optimisations. VERY IMPORTANT: the texts must be those
1073
returned by add_lines or data corruption can be caused.
1074
:param left_matching_blocks: a hint about which areas are common
1075
between the text and its left-hand-parent. The format is
1076
the SequenceMatcher.get_matching_blocks format.
1077
:param nostore_sha: Raise ExistingContent and do not add the lines to
1078
the versioned file if the digest of the lines matches this.
1079
:param random_id: If True a random id has been selected rather than
1080
an id determined by some deterministic process such as a converter
1081
from a foreign VCS. When True the backend may choose not to check
1082
for uniqueness of the resulting key within the versioned file, so
1083
this should only be done when the result is expected to be unique
1085
:param check_content: If True, the lines supplied are verified to be
1086
bytestrings that are correctly formed lines.
1087
:return: The text sha1, the number of bytes in the text, and an opaque
1088
representation of the inserted version which can be provided
1089
back to future add_lines calls in the parent_texts dictionary.
1091
raise NotImplementedError(self.add_content)
1093
981
def add_mpdiffs(self, records):
1094
982
"""Add mpdiffs to this VersionedFile.
1108
996
if not mpvf.has_version(p))
1109
997
# It seems likely that adding all the present parents as fulltexts can
1110
998
# easily exhaust memory.
999
chunks_to_lines = osutils.chunks_to_lines
1111
1000
for record in self.get_record_stream(needed_parents, 'unordered',
1113
1002
if record.storage_kind == 'absent':
1115
mpvf.add_version(record.get_bytes_as('lines'), record.key, [])
1004
mpvf.add_version(chunks_to_lines(record.get_bytes_as('chunked')),
1116
1006
for (key, parent_keys, expected_sha1, mpdiff), lines in zip(
1117
1007
records, mpvf.get_line_list(versions)):
1118
1008
if len(parent_keys) == 1:
1311
1201
self._mapper = mapper
1312
1202
self._is_locked = is_locked
1314
def add_content(self, factory, parent_texts=None,
1315
left_matching_blocks=None, nostore_sha=None, random_id=False):
1316
"""See VersionedFiles.add_content()."""
1317
lines = factory.get_bytes_as('lines')
1318
return self.add_lines(
1319
factory.key, factory.parents, lines,
1320
parent_texts=parent_texts,
1321
left_matching_blocks=left_matching_blocks,
1322
nostore_sha=nostore_sha,
1323
random_id=random_id,
1326
1204
def add_lines(self, key, parents, lines, parent_texts=None,
1327
1205
left_matching_blocks=None, nostore_sha=None, random_id=False,
1328
1206
check_content=True):
1600
1478
ver_a, base, self, (self._file_id,), graph).plan_merge()
1601
1479
return _PlanLCAMerge._subtract_plans(list(old_plan), list(new_plan))
1603
def add_content(self, factory):
1604
return self.add_lines(
1605
factory.key, factory.parents, factory.get_bytes_as('lines'))
1607
1481
def add_lines(self, key, parents, lines):
1608
1482
"""See VersionedFiles.add_lines
1628
1502
lines = self._lines[key]
1629
1503
parents = self._parents[key]
1630
1504
pending.remove(key)
1631
yield ChunkedContentFactory(
1632
key, parents, None, lines,
1633
chunks_are_lines=True)
1505
yield ChunkedContentFactory(key, parents, None, lines)
1634
1506
for versionedfile in self.fallback_versionedfiles:
1635
1507
for record in versionedfile.get_record_stream(
1636
1508
pending, 'unordered', True):
1859
1731
if lines is not None:
1860
1732
if not isinstance(lines, list):
1861
1733
raise AssertionError
1862
yield ChunkedContentFactory(
1863
(k,), None, sha1=osutils.sha_strings(lines),
1864
chunks=lines, chunks_are_lines=True)
1734
yield ChunkedContentFactory((k,), None,
1735
sha1=osutils.sha_strings(lines),
1866
1738
yield AbsentContentFactory((k,))