624
756
out.extend(lines)
627
def annotate(self, knit, version_id):
759
def annotate(self, knit, key):
628
760
annotator = _KnitAnnotator(knit)
629
return annotator.annotate(version_id)
632
def make_empty_knit(transport, relpath):
633
"""Construct a empty knit at the specified location."""
634
k = make_file_knit(transport, relpath, 'w', KnitPlainFactory)
637
def make_file_knit(name, transport, file_mode=None, access_mode='w',
638
factory=None, delta=True, create=False, create_parent_dir=False,
639
delay_create=False, dir_mode=None, get_scope=None):
640
"""Factory to create a KnitVersionedFile for a .knit/.kndx file pair."""
642
factory = KnitAnnotateFactory()
643
if get_scope is None:
644
get_scope = lambda:None
645
index = _KnitIndex(transport, name + INDEX_SUFFIX,
646
access_mode, create=create, file_mode=file_mode,
647
create_parent_dir=create_parent_dir, delay_create=delay_create,
648
dir_mode=dir_mode, get_scope=get_scope)
649
access = _KnitAccess(transport, name + DATA_SUFFIX, file_mode,
650
dir_mode, ((create and not len(index)) and delay_create),
652
return KnitVersionedFile(name, transport, factory=factory,
653
create=create, delay_create=delay_create, index=index,
654
access_method=access)
658
"""Return the suffixes used by file based knits."""
659
return [DATA_SUFFIX, INDEX_SUFFIX]
660
make_file_knit.get_suffixes = get_suffixes
663
class KnitVersionedFile(VersionedFile):
664
"""Weave-like structure with faster random access.
666
A knit stores a number of texts and a summary of the relationships
667
between them. Texts are identified by a string version-id. Texts
668
are normally stored and retrieved as a series of lines, but can
669
also be passed as single strings.
671
Lines are stored with the trailing newline (if any) included, to
672
avoid special cases for files with no final newline. Lines are
673
composed of 8-bit characters, not unicode. The combination of
674
these approaches should mean any 'binary' file can be safely
675
stored and retrieved.
678
def __init__(self, relpath, transport, file_mode=None,
679
factory=None, delta=True, create=False, create_parent_dir=False,
680
delay_create=False, dir_mode=None, index=None, access_method=None):
681
"""Construct a knit at location specified by relpath.
683
:param create: If not True, only open an existing knit.
684
:param create_parent_dir: If True, create the parent directory if
685
creating the file fails. (This is used for stores with
686
hash-prefixes that may not exist yet)
687
:param delay_create: The calling code is aware that the knit won't
688
actually be created until the first data is stored.
689
:param index: An index to use for the knit.
761
return annotator.annotate(key)
765
def make_file_factory(annotated, mapper):
766
"""Create a factory for creating a file based KnitVersionedFiles.
768
This is only functional enough to run interface tests, it doesn't try to
769
provide a full pack environment.
771
:param annotated: knit annotations are wanted.
772
:param mapper: The mapper from keys to paths.
774
def factory(transport):
775
index = _KndxIndex(transport, mapper, lambda:None, lambda:True, lambda:True)
776
access = _KnitKeyAccess(transport, mapper)
777
return KnitVersionedFiles(index, access, annotated=annotated)
781
def make_pack_factory(graph, delta, keylength):
782
"""Create a factory for creating a pack based VersionedFiles.
784
This is only functional enough to run interface tests, it doesn't try to
785
provide a full pack environment.
787
:param graph: Store a graph.
788
:param delta: Delta compress contents.
789
:param keylength: How long should keys be.
791
def factory(transport):
792
parents = graph or delta
798
max_delta_chain = 200
801
graph_index = _mod_index.InMemoryGraphIndex(reference_lists=ref_length,
802
key_elements=keylength)
803
stream = transport.open_write_stream('newpack')
804
writer = pack.ContainerWriter(stream.write)
806
index = _KnitGraphIndex(graph_index, lambda:True, parents=parents,
807
deltas=delta, add_callback=graph_index.add_nodes)
808
access = _DirectPackAccess({})
809
access.set_writer(writer, graph_index, (transport, 'newpack'))
810
result = KnitVersionedFiles(index, access,
811
max_delta_chain=max_delta_chain)
812
result.stream = stream
813
result.writer = writer
818
def cleanup_pack_knit(versioned_files):
819
versioned_files.stream.close()
820
versioned_files.writer.end()
823
def _get_total_build_size(self, keys, positions):
824
"""Determine the total bytes to build these keys.
826
(helper function because _KnitGraphIndex and _KndxIndex work the same, but
827
don't inherit from a common base.)
829
:param keys: Keys that we want to build
830
:param positions: dict of {key, (info, index_memo, comp_parent)} (such
831
as returned by _get_components_positions)
832
:return: Number of bytes to build those keys
834
all_build_index_memos = {}
838
for key in build_keys:
839
# This is mostly for the 'stacked' case
840
# Where we will be getting the data from a fallback
841
if key not in positions:
843
_, index_memo, compression_parent = positions[key]
844
all_build_index_memos[key] = index_memo
845
if compression_parent not in all_build_index_memos:
846
next_keys.add(compression_parent)
847
build_keys = next_keys
848
return sum([index_memo[2] for index_memo
849
in all_build_index_memos.itervalues()])
852
class KnitVersionedFiles(VersionedFiles):
853
"""Storage for many versioned files using knit compression.
855
Backend storage is managed by indices and data objects.
857
:ivar _index: A _KnitGraphIndex or similar that can describe the
858
parents, graph, compression and data location of entries in this
859
KnitVersionedFiles. Note that this is only the index for
860
*this* vfs; if there are fallbacks they must be queried separately.
863
def __init__(self, index, data_access, max_delta_chain=200,
864
annotated=False, reload_func=None):
865
"""Create a KnitVersionedFiles with index and data_access.
867
:param index: The index for the knit data.
868
:param data_access: The access object to store and retrieve knit
870
:param max_delta_chain: The maximum number of deltas to permit during
871
insertion. Set to 0 to prohibit the use of deltas.
872
:param annotated: Set to True to cause annotations to be calculated and
873
stored during insertion.
874
:param reload_func: An function that can be called if we think we need
875
to reload the pack listing and try again. See
876
'bzrlib.repofmt.pack_repo.AggregateIndex' for the signature.
691
super(KnitVersionedFile, self).__init__()
692
self.transport = transport
693
self.filename = relpath
694
self.factory = factory or KnitAnnotateFactory()
697
self._max_delta_chain = 200
699
if None in (access_method, index):
700
raise ValueError("No default access_method or index any more")
701
878
self._index = index
702
_access = access_method
703
if create and not len(self) and not delay_create:
705
self._data = _KnitData(_access)
879
self._access = data_access
880
self._max_delta_chain = max_delta_chain
882
self._factory = KnitAnnotateFactory()
884
self._factory = KnitPlainFactory()
885
self._fallback_vfs = []
886
self._reload_func = reload_func
707
888
def __repr__(self):
708
return '%s(%s)' % (self.__class__.__name__,
709
self.transport.abspath(self.filename))
711
def _check_should_delta(self, first_parents):
889
return "%s(%r, %r)" % (
890
self.__class__.__name__,
894
def add_fallback_versioned_files(self, a_versioned_files):
895
"""Add a source of texts for texts not present in this knit.
897
:param a_versioned_files: A VersionedFiles object.
899
self._fallback_vfs.append(a_versioned_files)
901
def add_lines(self, key, parents, lines, parent_texts=None,
902
left_matching_blocks=None, nostore_sha=None, random_id=False,
904
"""See VersionedFiles.add_lines()."""
905
self._index._check_write_ok()
906
self._check_add(key, lines, random_id, check_content)
908
# The caller might pass None if there is no graph data, but kndx
909
# indexes can't directly store that, so we give them
910
# an empty tuple instead.
912
return self._add(key, lines, parents,
913
parent_texts, left_matching_blocks, nostore_sha, random_id)
915
def _add(self, key, lines, parents, parent_texts,
916
left_matching_blocks, nostore_sha, random_id):
917
"""Add a set of lines on top of version specified by parents.
919
Any versions not present will be converted into ghosts.
921
# first thing, if the content is something we don't need to store, find
923
line_bytes = ''.join(lines)
924
digest = sha_string(line_bytes)
925
if nostore_sha == digest:
926
raise errors.ExistingContent
929
if parent_texts is None:
931
# Do a single query to ascertain parent presence; we only compress
932
# against parents in the same kvf.
933
present_parent_map = self._index.get_parent_map(parents)
934
for parent in parents:
935
if parent in present_parent_map:
936
present_parents.append(parent)
938
# Currently we can only compress against the left most present parent.
939
if (len(present_parents) == 0 or
940
present_parents[0] != parents[0]):
943
# To speed the extract of texts the delta chain is limited
944
# to a fixed number of deltas. This should minimize both
945
# I/O and the time spend applying deltas.
946
delta = self._check_should_delta(present_parents[0])
948
text_length = len(line_bytes)
951
if lines[-1][-1] != '\n':
952
# copy the contents of lines.
954
options.append('no-eol')
955
lines[-1] = lines[-1] + '\n'
958
for element in key[:-1]:
959
if type(element) != str:
960
raise TypeError("key contains non-strings: %r" % (key,))
962
key = key[:-1] + ('sha1:' + digest,)
963
elif type(key[-1]) != str:
964
raise TypeError("key contains non-strings: %r" % (key,))
965
# Knit hunks are still last-element only
967
content = self._factory.make(lines, version_id)
968
if 'no-eol' in options:
969
# Hint to the content object that its text() call should strip the
971
content._should_strip_eol = True
972
if delta or (self._factory.annotated and len(present_parents) > 0):
973
# Merge annotations from parent texts if needed.
974
delta_hunks = self._merge_annotations(content, present_parents,
975
parent_texts, delta, self._factory.annotated,
976
left_matching_blocks)
979
options.append('line-delta')
980
store_lines = self._factory.lower_line_delta(delta_hunks)
981
size, bytes = self._record_to_data(key, digest,
984
options.append('fulltext')
985
# isinstance is slower and we have no hierarchy.
986
if self._factory.__class__ is KnitPlainFactory:
987
# Use the already joined bytes saving iteration time in
989
size, bytes = self._record_to_data(key, digest,
992
# get mixed annotation + content and feed it into the
994
store_lines = self._factory.lower_fulltext(content)
995
size, bytes = self._record_to_data(key, digest,
998
access_memo = self._access.add_raw_records([(key, size)], bytes)[0]
999
self._index.add_records(
1000
((key, options, access_memo, parents),),
1001
random_id=random_id)
1002
return digest, text_length, content
1004
def annotate(self, key):
1005
"""See VersionedFiles.annotate."""
1006
return self._factory.annotate(self, key)
1008
def check(self, progress_bar=None):
1009
"""See VersionedFiles.check()."""
1010
# This doesn't actually test extraction of everything, but that will
1011
# impact 'bzr check' substantially, and needs to be integrated with
1012
# care. However, it does check for the obvious problem of a delta with
1014
keys = self._index.keys()
1015
parent_map = self.get_parent_map(keys)
1017
if self._index.get_method(key) != 'fulltext':
1018
compression_parent = parent_map[key][0]
1019
if compression_parent not in parent_map:
1020
raise errors.KnitCorrupt(self,
1021
"Missing basis parent %s for %s" % (
1022
compression_parent, key))
1023
for fallback_vfs in self._fallback_vfs:
1024
fallback_vfs.check()
1026
def _check_add(self, key, lines, random_id, check_content):
1027
"""check that version_id and lines are safe to add."""
1028
version_id = key[-1]
1029
if version_id is not None:
1030
if contains_whitespace(version_id):
1031
raise InvalidRevisionId(version_id, self)
1032
self.check_not_reserved_id(version_id)
1033
# TODO: If random_id==False and the key is already present, we should
1034
# probably check that the existing content is identical to what is
1035
# being inserted, and otherwise raise an exception. This would make
1036
# the bundle code simpler.
1038
self._check_lines_not_unicode(lines)
1039
self._check_lines_are_lines(lines)
1041
def _check_header(self, key, line):
1042
rec = self._split_header(line)
1043
self._check_header_version(rec, key[-1])
1046
def _check_header_version(self, rec, version_id):
1047
"""Checks the header version on original format knit records.
1049
These have the last component of the key embedded in the record.
1051
if rec[1] != version_id:
1052
raise KnitCorrupt(self,
1053
'unexpected version, wanted %r, got %r' % (version_id, rec[1]))
1055
def _check_should_delta(self, parent):
712
1056
"""Iterate back through the parent listing, looking for a fulltext.
714
1058
This is used when we want to decide whether to add a delta or a new
723
1067
fulltext_size = None
724
delta_parents = first_parents
725
1068
for count in xrange(self._max_delta_chain):
726
parent = delta_parents[0]
727
method = self._index.get_method(parent)
728
index, pos, size = self._index.get_position(parent)
729
if method == 'fulltext':
1070
# Note that this only looks in the index of this particular
1071
# KnitVersionedFiles, not in the fallbacks. This ensures that
1072
# we won't store a delta spanning physical repository
1074
build_details = self._index.get_build_details([parent])
1075
parent_details = build_details[parent]
1076
except (RevisionNotPresent, KeyError), e:
1077
# Some basis is not locally present: always fulltext
1079
index_memo, compression_parent, _, _ = parent_details
1080
_, _, size = index_memo
1081
if compression_parent is None:
730
1082
fulltext_size = size
732
1084
delta_size += size
733
delta_parents = self._index.get_parent_map([parent])[parent]
1085
# We don't explicitly check for presence because this is in an
1086
# inner loop, and if it's missing it'll fail anyhow.
1087
parent = compression_parent
735
1089
# We couldn't find a fulltext, so we must create a new one
1091
# Simple heuristic - if the total I/O wold be greater as a delta than
1092
# the originally installed fulltext, we create a new fulltext.
738
1093
return fulltext_size > delta_size
740
def _check_write_ok(self):
741
return self._index._check_write_ok()
743
def _add_raw_records(self, records, data):
744
"""Add all the records 'records' with data pre-joined in 'data'.
746
:param records: A list of tuples(version_id, options, parents, size).
747
:param data: The data for the records. When it is written, the records
748
are adjusted to have pos pointing into data by the sum of
749
the preceding records sizes.
752
raw_record_sizes = [record[3] for record in records]
753
positions = self._data.add_raw_records(raw_record_sizes, data)
755
for (version_id, options, parents, _), access_memo in zip(
757
index_entries.append((version_id, options, access_memo, parents))
758
self._index.add_versions(index_entries)
760
def copy_to(self, name, transport):
761
"""See VersionedFile.copy_to()."""
762
# copy the current index to a temp index to avoid racing with local
764
transport.put_file_non_atomic(name + INDEX_SUFFIX + '.tmp',
765
self.transport.get(self._index._filename))
767
f = self._data._open_file()
769
transport.put_file(name + DATA_SUFFIX, f)
772
# move the copied index into place
773
transport.move(name + INDEX_SUFFIX + '.tmp', name + INDEX_SUFFIX)
775
def get_data_stream(self, required_versions):
776
"""Get a data stream for the specified versions.
778
Versions may be returned in any order, not necessarily the order
779
specified. They are returned in a partial order by compression
780
parent, so that the deltas can be applied as the data stream is
781
inserted; however note that compression parents will not be sent
782
unless they were specifically requested, as the client may already
785
:param required_versions: The exact set of versions to be extracted.
786
Unlike some other knit methods, this is not used to generate a
787
transitive closure, rather it is used precisely as given.
789
:returns: format_signature, list of (version, options, length, parents),
792
required_version_set = frozenset(required_versions)
794
# list of revisions that can just be sent without waiting for their
797
# map from revision to the children based on it
799
# first, read all relevant index data, enough to sort into the right
801
for version_id in required_versions:
802
options = self._index.get_options(version_id)
803
parents = self._index.get_parents_with_ghosts(version_id)
804
index_memo = self._index.get_position(version_id)
805
version_index[version_id] = (index_memo, options, parents)
806
if ('line-delta' in options
807
and parents[0] in required_version_set):
808
# must wait until the parent has been sent
809
deferred.setdefault(parents[0], []). \
812
# either a fulltext, or a delta whose parent the client did
813
# not ask for and presumably already has
814
ready_to_send.append(version_id)
815
# build a list of results to return, plus instructions for data to
817
copy_queue_records = []
818
temp_version_list = []
820
# XXX: pushing and popping lists may be a bit inefficient
821
version_id = ready_to_send.pop(0)
822
(index_memo, options, parents) = version_index[version_id]
823
copy_queue_records.append((version_id, index_memo))
824
none, data_pos, data_size = index_memo
825
temp_version_list.append((version_id, options, data_size,
827
if version_id in deferred:
828
# now we can send all the children of this revision - we could
829
# put them in anywhere, but we hope that sending them soon
830
# after the fulltext will give good locality in the receiver
831
ready_to_send[:0] = deferred.pop(version_id)
832
if not (len(deferred) == 0):
833
raise AssertionError("Still have compressed child versions waiting to be sent")
834
# XXX: The stream format is such that we cannot stream it - we have to
835
# know the length of all the data a-priori.
837
result_version_list = []
838
for (version_id, raw_data, _), \
839
(version_id2, options, _, parents) in \
840
izip(self._data.read_records_iter_raw(copy_queue_records),
842
if not (version_id == version_id2):
843
raise AssertionError('logic error, inconsistent results')
844
raw_datum.append(raw_data)
845
result_version_list.append(
846
(version_id, options, len(raw_data), parents))
847
# provide a callback to get data incrementally.
848
pseudo_file = StringIO(''.join(raw_datum))
851
return pseudo_file.read()
853
return pseudo_file.read(length)
854
return (self.get_format_signature(), result_version_list, read)
856
def get_record_stream(self, versions, ordering, include_delta_closure):
857
"""Get a stream of records for versions.
859
:param versions: The versions to include. Each version is a tuple
1095
def _build_details_to_components(self, build_details):
1096
"""Convert a build_details tuple to a position tuple."""
1097
# record_details, access_memo, compression_parent
1098
return build_details[3], build_details[0], build_details[1]
1100
def _get_components_positions(self, keys, allow_missing=False):
1101
"""Produce a map of position data for the components of keys.
1103
This data is intended to be used for retrieving the knit records.
1105
A dict of key to (record_details, index_memo, next, parents) is
1107
method is the way referenced data should be applied.
1108
index_memo is the handle to pass to the data access to actually get the
1110
next is the build-parent of the version, or None for fulltexts.
1111
parents is the version_ids of the parents of this version
1113
:param allow_missing: If True do not raise an error on a missing component,
1117
pending_components = keys
1118
while pending_components:
1119
build_details = self._index.get_build_details(pending_components)
1120
current_components = set(pending_components)
1121
pending_components = set()
1122
for key, details in build_details.iteritems():
1123
(index_memo, compression_parent, parents,
1124
record_details) = details
1125
method = record_details[0]
1126
if compression_parent is not None:
1127
pending_components.add(compression_parent)
1128
component_data[key] = self._build_details_to_components(details)
1129
missing = current_components.difference(build_details)
1130
if missing and not allow_missing:
1131
raise errors.RevisionNotPresent(missing.pop(), self)
1132
return component_data
1134
def _get_content(self, key, parent_texts={}):
1135
"""Returns a content object that makes up the specified
1137
cached_version = parent_texts.get(key, None)
1138
if cached_version is not None:
1139
# Ensure the cache dict is valid.
1140
if not self.get_parent_map([key]):
1141
raise RevisionNotPresent(key, self)
1142
return cached_version
1143
generator = _VFContentMapGenerator(self, [key])
1144
return generator._get_content(key)
1146
def get_parent_map(self, keys):
1147
"""Get a map of the graph parents of keys.
1149
:param keys: The keys to look up parents for.
1150
:return: A mapping from keys to parents. Absent keys are absent from
1153
return self._get_parent_map_with_sources(keys)[0]
1155
def _get_parent_map_with_sources(self, keys):
1156
"""Get a map of the parents of keys.
1158
:param keys: The keys to look up parents for.
1159
:return: A tuple. The first element is a mapping from keys to parents.
1160
Absent keys are absent from the mapping. The second element is a
1161
list with the locations each key was found in. The first element
1162
is the in-this-knit parents, the second the first fallback source,
1166
sources = [self._index] + self._fallback_vfs
1169
for source in sources:
1172
new_result = source.get_parent_map(missing)
1173
source_results.append(new_result)
1174
result.update(new_result)
1175
missing.difference_update(set(new_result))
1176
return result, source_results
1178
def _get_record_map(self, keys, allow_missing=False):
1179
"""Produce a dictionary of knit records.
1181
:return: {key:(record, record_details, digest, next)}
1183
data returned from read_records (a KnitContentobject)
1185
opaque information to pass to parse_record
1187
SHA1 digest of the full text after all steps are done
1189
build-parent of the version, i.e. the leftmost ancestor.
1190
Will be None if the record is not a delta.
1191
:param keys: The keys to build a map for
1192
:param allow_missing: If some records are missing, rather than
1193
error, just return the data that could be generated.
1195
raw_map = self._get_record_map_unparsed(keys,
1196
allow_missing=allow_missing)
1197
return self._raw_map_to_record_map(raw_map)
1199
def _raw_map_to_record_map(self, raw_map):
1200
"""Parse the contents of _get_record_map_unparsed.
1202
:return: see _get_record_map.
1206
data, record_details, next = raw_map[key]
1207
content, digest = self._parse_record(key[-1], data)
1208
result[key] = content, record_details, digest, next
1211
def _get_record_map_unparsed(self, keys, allow_missing=False):
1212
"""Get the raw data for reconstructing keys without parsing it.
1214
:return: A dict suitable for parsing via _raw_map_to_record_map.
1215
key-> raw_bytes, (method, noeol), compression_parent
1217
# This retries the whole request if anything fails. Potentially we
1218
# could be a bit more selective. We could track the keys whose records
1219
# we have successfully found, and then only request the new records
1220
# from there. However, _get_components_positions grabs the whole build
1221
# chain, which means we'll likely try to grab the same records again
1222
# anyway. Also, can the build chains change as part of a pack
1223
# operation? We wouldn't want to end up with a broken chain.
1226
position_map = self._get_components_positions(keys,
1227
allow_missing=allow_missing)
1228
# key = component_id, r = record_details, i_m = index_memo,
1230
records = [(key, i_m) for key, (r, i_m, n)
1231
in position_map.iteritems()]
1232
# Sort by the index memo, so that we request records from the
1233
# same pack file together, and in forward-sorted order
1234
records.sort(key=operator.itemgetter(1))
1236
for key, data in self._read_records_iter_unchecked(records):
1237
(record_details, index_memo, next) = position_map[key]
1238
raw_record_map[key] = data, record_details, next
1239
return raw_record_map
1240
except errors.RetryWithNewPacks, e:
1241
self._access.reload_or_raise(e)
1244
def _split_by_prefix(cls, keys):
1245
"""For the given keys, split them up based on their prefix.
1247
To keep memory pressure somewhat under control, split the
1248
requests back into per-file-id requests, otherwise "bzr co"
1249
extracts the full tree into memory before writing it to disk.
1250
This should be revisited if _get_content_maps() can ever cross
1253
The keys for a given file_id are kept in the same relative order.
1254
Ordering between file_ids is not, though prefix_order will return the
1255
order that the key was first seen.
1257
:param keys: An iterable of key tuples
1258
:return: (split_map, prefix_order)
1259
split_map A dictionary mapping prefix => keys
1260
prefix_order The order that we saw the various prefixes
1262
split_by_prefix = {}
1270
if prefix in split_by_prefix:
1271
split_by_prefix[prefix].append(key)
1273
split_by_prefix[prefix] = [key]
1274
prefix_order.append(prefix)
1275
return split_by_prefix, prefix_order
1277
def _group_keys_for_io(self, keys, non_local_keys, positions,
1278
_min_buffer_size=_STREAM_MIN_BUFFER_SIZE):
1279
"""For the given keys, group them into 'best-sized' requests.
1281
The idea is to avoid making 1 request per file, but to never try to
1282
unpack an entire 1.5GB source tree in a single pass. Also when
1283
possible, we should try to group requests to the same pack file
1286
:return: list of (keys, non_local) tuples that indicate what keys
1287
should be fetched next.
1289
# TODO: Ideally we would group on 2 factors. We want to extract texts
1290
# from the same pack file together, and we want to extract all
1291
# the texts for a given build-chain together. Ultimately it
1292
# probably needs a better global view.
1293
total_keys = len(keys)
1294
prefix_split_keys, prefix_order = self._split_by_prefix(keys)
1295
prefix_split_non_local_keys, _ = self._split_by_prefix(non_local_keys)
1297
cur_non_local = set()
1301
for prefix in prefix_order:
1302
keys = prefix_split_keys[prefix]
1303
non_local = prefix_split_non_local_keys.get(prefix, [])
1305
this_size = self._index._get_total_build_size(keys, positions)
1306
cur_size += this_size
1307
cur_keys.extend(keys)
1308
cur_non_local.update(non_local)
1309
if cur_size > _min_buffer_size:
1310
result.append((cur_keys, cur_non_local))
1311
sizes.append(cur_size)
1313
cur_non_local = set()
1316
result.append((cur_keys, cur_non_local))
1317
sizes.append(cur_size)
1320
def get_record_stream(self, keys, ordering, include_delta_closure):
1321
"""Get a stream of records for keys.
1323
:param keys: The keys to include.
861
1324
:param ordering: Either 'unordered' or 'topological'. A topologically
862
1325
sorted stream has compression parents strictly before their
866
1329
:return: An iterator of ContentFactory objects, each of which is only
867
1330
valid until the iterator is advanced.
869
if include_delta_closure:
870
# Nb: what we should do is plan the data to stream to allow
871
# reconstruction of all the texts without excessive buffering,
872
# including re-sending common bases as needed. This makes the most
873
# sense when we start serialising these streams though, so for now
874
# we just fallback to individual text construction behind the
875
# abstraction barrier.
879
# We end up doing multiple index lookups here for parents details and
880
# disk layout details - we need a unified api ?
881
parent_map = self.get_parent_map(versions)
882
absent_versions = set(versions) - set(parent_map)
883
if ordering == 'topological':
884
present_versions = topo_sort(parent_map)
886
# List comprehension to keep the requested order (as that seems
887
# marginally useful, at least until we start doing IO optimising
889
present_versions = [version for version in versions if version in
891
position_map = self._get_components_positions(present_versions)
892
records = [(version, position_map[version][1]) for version in
895
for version in absent_versions:
896
yield AbsentContentFactory((version,))
897
for version, raw_data, sha1 in \
898
self._data.read_records_iter_raw(records):
899
(record_details, index_memo, _) = position_map[version]
900
yield KnitContentFactory(version, parent_map[version],
901
record_details, sha1, raw_data, self.factory.annotated, knit)
903
def _extract_blocks(self, version_id, source, target):
904
if self._index.get_method(version_id) != 'line-delta':
906
parent, sha1, noeol, delta = self.get_delta(version_id)
907
return KnitContent.get_line_delta_blocks(delta, source, target)
909
def get_delta(self, version_id):
910
"""Get a delta for constructing version from some other version."""
911
self.check_not_reserved_id(version_id)
912
parents = self.get_parent_map([version_id])[version_id]
917
index_memo = self._index.get_position(version_id)
918
data, sha1 = self._data.read_records(((version_id, index_memo),))[version_id]
919
noeol = 'no-eol' in self._index.get_options(version_id)
920
if 'fulltext' == self._index.get_method(version_id):
921
new_content = self.factory.parse_fulltext(data, version_id)
922
if parent is not None:
923
reference_content = self._get_content(parent)
924
old_texts = reference_content.text()
927
new_texts = new_content.text()
928
delta_seq = patiencediff.PatienceSequenceMatcher(None, old_texts,
930
return parent, sha1, noeol, self._make_line_delta(delta_seq, new_content)
932
delta = self.factory.parse_line_delta(data, version_id)
933
return parent, sha1, noeol, delta
935
def get_format_signature(self):
936
"""See VersionedFile.get_format_signature()."""
937
if self.factory.annotated:
938
annotated_part = "annotated"
940
annotated_part = "plain"
941
return "knit-%s" % (annotated_part,)
943
def get_sha1s(self, version_ids):
944
"""See VersionedFile.get_sha1s()."""
945
record_map = self._get_record_map(version_ids)
946
# record entry 2 is the 'digest'.
947
return [record_map[v][2] for v in version_ids]
949
def insert_data_stream(self, (format, data_list, reader_callable)):
950
"""Insert knit records from a data stream into this knit.
952
If a version in the stream is already present in this knit, it will not
953
be inserted a second time. It will be checked for consistency with the
954
stored version however, and may cause a KnitCorrupt error to be raised
955
if the data in the stream disagrees with the already stored data.
957
:seealso: get_data_stream
959
if format != self.get_format_signature():
960
if 'knit' in debug.debug_flags:
962
'incompatible format signature inserting to %r', self)
963
source = self._knit_from_datastream(
964
(format, data_list, reader_callable))
965
stream = source.get_record_stream(source.versions(), 'unordered', False)
966
self.insert_record_stream(stream)
1332
# keys might be a generator
969
for version_id, options, length, parents in data_list:
970
if self.has_version(version_id):
971
# First check: the list of parents.
972
my_parents = self.get_parents_with_ghosts(version_id)
973
if tuple(my_parents) != tuple(parents):
974
# XXX: KnitCorrupt is not quite the right exception here.
977
'parents list %r from data stream does not match '
978
'already recorded parents %r for %s'
979
% (parents, my_parents, version_id))
981
# Also check the SHA-1 of the fulltext this content will
983
raw_data = reader_callable(length)
984
my_fulltext_sha1 = self.get_sha1s([version_id])[0]
985
df, rec = self._data._parse_record_header(version_id, raw_data)
986
stream_fulltext_sha1 = rec[3]
987
if my_fulltext_sha1 != stream_fulltext_sha1:
988
# Actually, we don't know if it's this knit that's corrupt,
989
# or the data stream we're trying to insert.
991
self.filename, 'sha-1 does not match %s' % version_id)
1336
if not self._index.has_graph:
1337
# Cannot sort when no graph has been stored.
1338
ordering = 'unordered'
1340
remaining_keys = keys
1343
keys = set(remaining_keys)
1344
for content_factory in self._get_remaining_record_stream(keys,
1345
ordering, include_delta_closure):
1346
remaining_keys.discard(content_factory.key)
1347
yield content_factory
1349
except errors.RetryWithNewPacks, e:
1350
self._access.reload_or_raise(e)
1352
def _get_remaining_record_stream(self, keys, ordering,
1353
include_delta_closure):
1354
"""This function is the 'retry' portion for get_record_stream."""
1355
if include_delta_closure:
1356
positions = self._get_components_positions(keys, allow_missing=True)
1358
build_details = self._index.get_build_details(keys)
1360
# (record_details, access_memo, compression_parent_key)
1361
positions = dict((key, self._build_details_to_components(details))
1362
for key, details in build_details.iteritems())
1363
absent_keys = keys.difference(set(positions))
1364
# There may be more absent keys : if we're missing the basis component
1365
# and are trying to include the delta closure.
1366
# XXX: We should not ever need to examine remote sources because we do
1367
# not permit deltas across versioned files boundaries.
1368
if include_delta_closure:
1369
needed_from_fallback = set()
1370
# Build up reconstructable_keys dict. key:True in this dict means
1371
# the key can be reconstructed.
1372
reconstructable_keys = {}
1376
chain = [key, positions[key][2]]
1378
needed_from_fallback.add(key)
1381
while chain[-1] is not None:
1382
if chain[-1] in reconstructable_keys:
1383
result = reconstructable_keys[chain[-1]]
1387
chain.append(positions[chain[-1]][2])
1389
# missing basis component
1390
needed_from_fallback.add(chain[-1])
1393
for chain_key in chain[:-1]:
1394
reconstructable_keys[chain_key] = result
1396
needed_from_fallback.add(key)
1397
# Double index lookups here : need a unified api ?
1398
global_map, parent_maps = self._get_parent_map_with_sources(keys)
1399
if ordering in ('topological', 'groupcompress'):
1400
if ordering == 'topological':
1401
# Global topological sort
1402
present_keys = tsort.topo_sort(global_map)
993
if 'line-delta' in options:
994
# Make sure that this knit record is actually useful: a
995
# line-delta is no use unless we have its parent.
996
# Fetching from a broken repository with this problem
997
# shouldn't break the target repository.
999
# See https://bugs.launchpad.net/bzr/+bug/164443
1000
if not self._index.has_version(parents[0]):
1003
'line-delta from stream '
1006
'missing parent %s\n'
1007
'Try running "bzr check" '
1008
'on the source repository, and "bzr reconcile" '
1010
(version_id, parents[0]))
1012
# We received a line-delta record for a non-delta knit.
1013
# Convert it to a fulltext.
1014
gzip_bytes = reader_callable(length)
1015
self._convert_line_delta_to_fulltext(
1016
gzip_bytes, version_id, parents)
1019
self._add_raw_records(
1020
[(version_id, options, parents, length)],
1021
reader_callable(length))
1023
def _convert_line_delta_to_fulltext(self, gzip_bytes, version_id, parents):
1024
lines, sha1 = self._data._parse_record(version_id, gzip_bytes)
1025
delta = self.factory.parse_line_delta(lines, version_id)
1026
content = self.factory.make(self.get_lines(parents[0]), parents[0])
1027
content.apply_delta(delta, version_id)
1028
digest, len, content = self.add_lines(
1029
version_id, parents, content.text())
1031
raise errors.VersionedFileInvalidChecksum(version_id)
1033
def _knit_from_datastream(self, (format, data_list, reader_callable)):
1034
"""Create a knit object from a data stream.
1036
This method exists to allow conversion of data streams that do not
1037
match the signature of this knit. Generally it will be slower and use
1038
more memory to use this method to insert data, but it will work.
1040
:seealso: get_data_stream for details on datastreams.
1041
:return: A knit versioned file which can be used to join the datastream
1044
if format == "knit-plain":
1045
factory = KnitPlainFactory()
1046
elif format == "knit-annotated":
1047
factory = KnitAnnotateFactory()
1049
raise errors.KnitDataStreamUnknown(format)
1050
index = _StreamIndex(data_list, self._index)
1051
access = _StreamAccess(reader_callable, index, self, factory)
1052
return KnitVersionedFile(self.filename, self.transport,
1053
factory=factory, index=index, access_method=access)
1404
present_keys = sort_groupcompress(global_map)
1405
# Now group by source:
1407
current_source = None
1408
for key in present_keys:
1409
for parent_map in parent_maps:
1410
if key in parent_map:
1411
key_source = parent_map
1413
if current_source is not key_source:
1414
source_keys.append((key_source, []))
1415
current_source = key_source
1416
source_keys[-1][1].append(key)
1418
if ordering != 'unordered':
1419
raise AssertionError('valid values for ordering are:'
1420
' "unordered", "groupcompress" or "topological" not: %r'
1422
# Just group by source; remote sources first.
1425
for parent_map in reversed(parent_maps):
1426
source_keys.append((parent_map, []))
1427
for key in parent_map:
1428
present_keys.append(key)
1429
source_keys[-1][1].append(key)
1430
# We have been requested to return these records in an order that
1431
# suits us. So we ask the index to give us an optimally sorted
1433
for source, sub_keys in source_keys:
1434
if source is parent_maps[0]:
1435
# Only sort the keys for this VF
1436
self._index._sort_keys_by_io(sub_keys, positions)
1437
absent_keys = keys - set(global_map)
1438
for key in absent_keys:
1439
yield AbsentContentFactory(key)
1440
# restrict our view to the keys we can answer.
1441
# XXX: Memory: TODO: batch data here to cap buffered data at (say) 1MB.
1442
# XXX: At that point we need to consider the impact of double reads by
1443
# utilising components multiple times.
1444
if include_delta_closure:
1445
# XXX: get_content_maps performs its own index queries; allow state
1447
non_local_keys = needed_from_fallback - absent_keys
1448
for keys, non_local_keys in self._group_keys_for_io(present_keys,
1451
generator = _VFContentMapGenerator(self, keys, non_local_keys,
1453
for record in generator.get_record_stream():
1456
for source, keys in source_keys:
1457
if source is parent_maps[0]:
1458
# this KnitVersionedFiles
1459
records = [(key, positions[key][1]) for key in keys]
1460
for key, raw_data, sha1 in self._read_records_iter_raw(records):
1461
(record_details, index_memo, _) = positions[key]
1462
yield KnitContentFactory(key, global_map[key],
1463
record_details, sha1, raw_data, self._factory.annotated, None)
1465
vf = self._fallback_vfs[parent_maps.index(source) - 1]
1466
for record in vf.get_record_stream(keys, ordering,
1467
include_delta_closure):
1470
def get_sha1s(self, keys):
1471
"""See VersionedFiles.get_sha1s()."""
1473
record_map = self._get_record_map(missing, allow_missing=True)
1475
for key, details in record_map.iteritems():
1476
if key not in missing:
1478
# record entry 2 is the 'digest'.
1479
result[key] = details[2]
1480
missing.difference_update(set(result))
1481
for source in self._fallback_vfs:
1484
new_result = source.get_sha1s(missing)
1485
result.update(new_result)
1486
missing.difference_update(set(new_result))
1055
1489
def insert_record_stream(self, stream):
1056
"""Insert a record stream into this versioned file.
1490
"""Insert a record stream into this container.
1058
:param stream: A stream of records to insert.
1492
:param stream: A stream of records to insert.
1060
:seealso VersionedFile.get_record_stream:
1494
:seealso VersionedFiles.get_record_stream:
1062
1496
def get_adapter(adapter_key):
1119
1584
# deprecated format this is tolerable. It can be fixed if
1120
1585
# needed by in the kndx index support raising on a duplicate
1121
1586
# add with identical parents and options.
1122
access_memo = self._data.add_raw_records([len(bytes)], bytes)[0]
1123
index_entry = (record.key[0], options, access_memo, parents)
1587
access_memo = self._access.add_raw_records(
1588
[(record.key, len(bytes))], bytes)[0]
1589
index_entry = (record.key, options, access_memo, parents)
1125
1590
if 'fulltext' not in options:
1126
basis_parent = parents[0]
1127
if not self.has_version(basis_parent):
1591
# Not a fulltext, so we need to make sure the compression
1592
# parent will also be present.
1593
# Note that pack backed knits don't need to buffer here
1594
# because they buffer all writes to the transaction level,
1595
# but we don't expose that difference at the index level. If
1596
# the query here has sufficient cost to show up in
1597
# profiling we should do that.
1599
# They're required to be physically in this
1600
# KnitVersionedFiles, not in a fallback.
1601
if not self._index.has_key(compression_parent):
1128
1602
pending = buffered_index_entries.setdefault(
1603
compression_parent, [])
1130
1604
pending.append(index_entry)
1131
1605
buffered = True
1132
1606
if not buffered:
1133
self._index.add_versions([index_entry])
1134
elif record.storage_kind == 'fulltext':
1135
self.add_lines(record.key[0], parents,
1136
split_lines(record.get_bytes_as('fulltext')))
1607
self._index.add_records([index_entry])
1608
elif record.storage_kind == 'chunked':
1609
self.add_lines(record.key, parents,
1610
osutils.chunks_to_lines(record.get_bytes_as('chunked')))
1138
adapter_key = record.storage_kind, 'fulltext'
1139
adapter = get_adapter(adapter_key)
1140
lines = split_lines(adapter.get_bytes(
1141
record, record.get_bytes_as(record.storage_kind)))
1143
self.add_lines(record.key[0], parents, lines)
1612
# Not suitable for direct insertion as a
1613
# delta, either because it's not the right format, or this
1614
# KnitVersionedFiles doesn't permit deltas (_max_delta_chain ==
1615
# 0) or because it depends on a base only present in the
1617
self._access.flush()
1619
# Try getting a fulltext directly from the record.
1620
bytes = record.get_bytes_as('fulltext')
1621
except errors.UnavailableRepresentation:
1622
adapter_key = record.storage_kind, 'fulltext'
1623
adapter = get_adapter(adapter_key)
1624
bytes = adapter.get_bytes(record)
1625
lines = split_lines(bytes)
1627
self.add_lines(record.key, parents, lines)
1144
1628
except errors.RevisionAlreadyPresent:
1146
1630
# Add any records whose basis parent is now available.
1147
added_keys = [record.key[0]]
1149
key = added_keys.pop(0)
1150
if key in buffered_index_entries:
1151
index_entries = buffered_index_entries[key]
1152
self._index.add_versions(index_entries)
1154
[index_entry[0] for index_entry in index_entries])
1155
del buffered_index_entries[key]
1156
# If there were any deltas which had a missing basis parent, error.
1632
added_keys = [record.key]
1634
key = added_keys.pop(0)
1635
if key in buffered_index_entries:
1636
index_entries = buffered_index_entries[key]
1637
self._index.add_records(index_entries)
1639
[index_entry[0] for index_entry in index_entries])
1640
del buffered_index_entries[key]
1157
1641
if buffered_index_entries:
1158
raise errors.RevisionNotPresent(buffered_index_entries.keys()[0],
1162
"""See VersionedFile.versions."""
1163
if 'evil' in debug.debug_flags:
1164
trace.mutter_callsite(2, "versions scales with size of history")
1165
return self._index.get_versions()
1167
def has_version(self, version_id):
1168
"""See VersionedFile.has_version."""
1169
if 'evil' in debug.debug_flags:
1170
trace.mutter_callsite(2, "has_version is a LBYL scenario")
1171
return self._index.has_version(version_id)
1173
__contains__ = has_version
1642
# There were index entries buffered at the end of the stream,
1643
# So these need to be added (if the index supports holding such
1644
# entries for later insertion)
1645
for key in buffered_index_entries:
1646
index_entries = buffered_index_entries[key]
1647
self._index.add_records(index_entries,
1648
missing_compression_parents=True)
1650
def get_missing_compression_parent_keys(self):
1651
"""Return an iterable of keys of missing compression parents.
1653
Check this after calling insert_record_stream to find out if there are
1654
any missing compression parents. If there are, the records that
1655
depend on them are not able to be inserted safely. For atomic
1656
KnitVersionedFiles built on packs, the transaction should be aborted or
1657
suspended - commit will fail at this point. Nonatomic knits will error
1658
earlier because they have no staging area to put pending entries into.
1660
return self._index.get_missing_compression_parents()
1662
def iter_lines_added_or_present_in_keys(self, keys, pb=None):
1663
"""Iterate over the lines in the versioned files from keys.
1665
This may return lines from other keys. Each item the returned
1666
iterator yields is a tuple of a line and a text version that that line
1667
is present in (not introduced in).
1669
Ordering of results is in whatever order is most suitable for the
1670
underlying storage format.
1672
If a progress bar is supplied, it may be used to indicate progress.
1673
The caller is responsible for cleaning up progress bars (because this
1677
* Lines are normalised by the underlying store: they will all have \\n
1679
* Lines are returned in arbitrary order.
1680
* If a requested key did not change any lines (or didn't have any
1681
lines), it may not be mentioned at all in the result.
1683
:param pb: Progress bar supplied by caller.
1684
:return: An iterator over (line, key).
1687
pb = progress.DummyProgress()
1693
# we don't care about inclusions, the caller cares.
1694
# but we need to setup a list of records to visit.
1695
# we need key, position, length
1697
build_details = self._index.get_build_details(keys)
1698
for key, details in build_details.iteritems():
1700
key_records.append((key, details[0]))
1701
records_iter = enumerate(self._read_records_iter(key_records))
1702
for (key_idx, (key, data, sha_value)) in records_iter:
1703
pb.update('Walking content', key_idx, total)
1704
compression_parent = build_details[key][1]
1705
if compression_parent is None:
1707
line_iterator = self._factory.get_fulltext_content(data)
1710
line_iterator = self._factory.get_linedelta_content(data)
1711
# Now that we are yielding the data for this key, remove it
1714
# XXX: It might be more efficient to yield (key,
1715
# line_iterator) in the future. However for now, this is a
1716
# simpler change to integrate into the rest of the
1717
# codebase. RBC 20071110
1718
for line in line_iterator:
1721
except errors.RetryWithNewPacks, e:
1722
self._access.reload_or_raise(e)
1723
# If there are still keys we've not yet found, we look in the fallback
1724
# vfs, and hope to find them there. Note that if the keys are found
1725
# but had no changes or no content, the fallback may not return
1727
if keys and not self._fallback_vfs:
1728
# XXX: strictly the second parameter is meant to be the file id
1729
# but it's not easily accessible here.
1730
raise RevisionNotPresent(keys, repr(self))
1731
for source in self._fallback_vfs:
1735
for line, key in source.iter_lines_added_or_present_in_keys(keys):
1736
source_keys.add(key)
1738
keys.difference_update(source_keys)
1739
pb.update('Walking content', total, total)
1741
def _make_line_delta(self, delta_seq, new_content):
1742
"""Generate a line delta from delta_seq and new_content."""
1744
for op in delta_seq.get_opcodes():
1745
if op[0] == 'equal':
1747
diff_hunks.append((op[1], op[2], op[4]-op[3], new_content._lines[op[3]:op[4]]))
1175
1750
def _merge_annotations(self, content, parents, parent_texts={},
1176
1751
delta=None, annotated=None,
1177
1752
left_matching_blocks=None):
1178
"""Merge annotations for content. This is done by comparing
1179
the annotations based on changed to the text.
1753
"""Merge annotations for content and generate deltas.
1755
This is done by comparing the annotations based on changes to the text
1756
and generating a delta on the resulting full texts. If annotations are
1757
not being created then a simple delta is created.
1181
1759
if left_matching_blocks is not None:
1182
1760
delta_seq = diff._PrematchedMatcher(left_matching_blocks)
1184
1762
delta_seq = None
1186
for parent_id in parents:
1187
merge_content = self._get_content(parent_id, parent_texts)
1188
if (parent_id == parents[0] and delta_seq is not None):
1764
for parent_key in parents:
1765
merge_content = self._get_content(parent_key, parent_texts)
1766
if (parent_key == parents[0] and delta_seq is not None):
1189
1767
seq = delta_seq
1191
1769
seq = patiencediff.PatienceSequenceMatcher(
1206
1792
None, old_texts, new_texts)
1207
1793
return self._make_line_delta(delta_seq, content)
1209
def _make_line_delta(self, delta_seq, new_content):
1210
"""Generate a line delta from delta_seq and new_content."""
1212
for op in delta_seq.get_opcodes():
1213
if op[0] == 'equal':
1215
diff_hunks.append((op[1], op[2], op[4]-op[3], new_content._lines[op[3]:op[4]]))
1218
def _get_components_positions(self, version_ids):
1219
"""Produce a map of position data for the components of versions.
1221
This data is intended to be used for retrieving the knit records.
1223
A dict of version_id to (record_details, index_memo, next, parents) is
1225
method is the way referenced data should be applied.
1226
index_memo is the handle to pass to the data access to actually get the
1228
next is the build-parent of the version, or None for fulltexts.
1229
parents is the version_ids of the parents of this version
1232
pending_components = version_ids
1233
while pending_components:
1234
build_details = self._index.get_build_details(pending_components)
1235
current_components = set(pending_components)
1236
pending_components = set()
1237
for version_id, details in build_details.iteritems():
1238
(index_memo, compression_parent, parents,
1239
record_details) = details
1240
method = record_details[0]
1241
if compression_parent is not None:
1242
pending_components.add(compression_parent)
1243
component_data[version_id] = (record_details, index_memo,
1245
missing = current_components.difference(build_details)
1247
raise errors.RevisionNotPresent(missing.pop(), self.filename)
1248
return component_data
1250
def _get_content(self, version_id, parent_texts={}):
1251
"""Returns a content object that makes up the specified
1253
cached_version = parent_texts.get(version_id, None)
1254
if cached_version is not None:
1255
if not self.has_version(version_id):
1256
raise RevisionNotPresent(version_id, self.filename)
1257
return cached_version
1259
text_map, contents_map = self._get_content_maps([version_id])
1260
return contents_map[version_id]
1262
def _check_versions_present(self, version_ids):
1263
"""Check that all specified versions are present."""
1264
self._index.check_versions_present(version_ids)
1266
def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts,
1267
nostore_sha, random_id, check_content, left_matching_blocks):
1268
"""See VersionedFile.add_lines_with_ghosts()."""
1269
self._check_add(version_id, lines, random_id, check_content)
1270
return self._add(version_id, lines, parents, self.delta,
1271
parent_texts, left_matching_blocks, nostore_sha, random_id)
1273
def _add_lines(self, version_id, parents, lines, parent_texts,
1274
left_matching_blocks, nostore_sha, random_id, check_content):
1275
"""See VersionedFile.add_lines."""
1276
self._check_add(version_id, lines, random_id, check_content)
1277
self._check_versions_present(parents)
1278
return self._add(version_id, lines[:], parents, self.delta,
1279
parent_texts, left_matching_blocks, nostore_sha, random_id)
1281
def _check_add(self, version_id, lines, random_id, check_content):
1282
"""check that version_id and lines are safe to add."""
1283
if contains_whitespace(version_id):
1284
raise InvalidRevisionId(version_id, self.filename)
1285
self.check_not_reserved_id(version_id)
1286
# Technically this could be avoided if we are happy to allow duplicate
1287
# id insertion when other things than bzr core insert texts, but it
1288
# seems useful for folk using the knit api directly to have some safety
1289
# blanket that we can disable.
1290
if not random_id and self.has_version(version_id):
1291
raise RevisionAlreadyPresent(version_id, self.filename)
1293
self._check_lines_not_unicode(lines)
1294
self._check_lines_are_lines(lines)
1296
def _add(self, version_id, lines, parents, delta, parent_texts,
1297
left_matching_blocks, nostore_sha, random_id):
1298
"""Add a set of lines on top of version specified by parents.
1300
If delta is true, compress the text as a line-delta against
1303
Any versions not present will be converted into ghosts.
1305
# first thing, if the content is something we don't need to store, find
1307
line_bytes = ''.join(lines)
1308
digest = sha_string(line_bytes)
1309
if nostore_sha == digest:
1310
raise errors.ExistingContent
1312
present_parents = []
1313
if parent_texts is None:
1315
for parent in parents:
1316
if self.has_version(parent):
1317
present_parents.append(parent)
1319
# can only compress against the left most present parent.
1321
(len(present_parents) == 0 or
1322
present_parents[0] != parents[0])):
1325
text_length = len(line_bytes)
1328
if lines[-1][-1] != '\n':
1329
# copy the contents of lines.
1331
options.append('no-eol')
1332
lines[-1] = lines[-1] + '\n'
1336
# To speed the extract of texts the delta chain is limited
1337
# to a fixed number of deltas. This should minimize both
1338
# I/O and the time spend applying deltas.
1339
delta = self._check_should_delta(present_parents)
1341
content = self.factory.make(lines, version_id)
1342
if delta or (self.factory.annotated and len(present_parents) > 0):
1343
# Merge annotations from parent texts if needed.
1344
delta_hunks = self._merge_annotations(content, present_parents,
1345
parent_texts, delta, self.factory.annotated,
1346
left_matching_blocks)
1349
options.append('line-delta')
1350
store_lines = self.factory.lower_line_delta(delta_hunks)
1351
size, bytes = self._data._record_to_data(version_id, digest,
1795
def _parse_record(self, version_id, data):
1796
"""Parse an original format knit record.
1798
These have the last element of the key only present in the stored data.
1800
rec, record_contents = self._parse_record_unchecked(data)
1801
self._check_header_version(rec, version_id)
1802
return record_contents, rec[3]
1804
def _parse_record_header(self, key, raw_data):
1805
"""Parse a record header for consistency.
1807
:return: the header and the decompressor stream.
1808
as (stream, header_record)
1810
df = tuned_gzip.GzipFile(mode='rb', fileobj=StringIO(raw_data))
1813
rec = self._check_header(key, df.readline())
1814
except Exception, e:
1815
raise KnitCorrupt(self,
1816
"While reading {%s} got %s(%s)"
1817
% (key, e.__class__.__name__, str(e)))
1820
def _parse_record_unchecked(self, data):
1822
# 4168 calls in 2880 217 internal
1823
# 4168 calls to _parse_record_header in 2121
1824
# 4168 calls to readlines in 330
1825
df = tuned_gzip.GzipFile(mode='rb', fileobj=StringIO(data))
1827
record_contents = df.readlines()
1828
except Exception, e:
1829
raise KnitCorrupt(self, "Corrupt compressed record %r, got %s(%s)" %
1830
(data, e.__class__.__name__, str(e)))
1831
header = record_contents.pop(0)
1832
rec = self._split_header(header)
1833
last_line = record_contents.pop()
1834
if len(record_contents) != int(rec[2]):
1835
raise KnitCorrupt(self,
1836
'incorrect number of lines %s != %s'
1837
' for version {%s} %s'
1838
% (len(record_contents), int(rec[2]),
1839
rec[1], record_contents))
1840
if last_line != 'end %s\n' % rec[1]:
1841
raise KnitCorrupt(self,
1842
'unexpected version end line %r, wanted %r'
1843
% (last_line, rec[1]))
1845
return rec, record_contents
1847
def _read_records_iter(self, records):
1848
"""Read text records from data file and yield result.
1850
The result will be returned in whatever is the fastest to read.
1851
Not by the order requested. Also, multiple requests for the same
1852
record will only yield 1 response.
1853
:param records: A list of (key, access_memo) entries
1854
:return: Yields (key, contents, digest) in the order
1855
read, not the order requested
1860
# XXX: This smells wrong, IO may not be getting ordered right.
1861
needed_records = sorted(set(records), key=operator.itemgetter(1))
1862
if not needed_records:
1865
# The transport optimizes the fetching as well
1866
# (ie, reads continuous ranges.)
1867
raw_data = self._access.get_raw_records(
1868
[index_memo for key, index_memo in needed_records])
1870
for (key, index_memo), data in \
1871
izip(iter(needed_records), raw_data):
1872
content, digest = self._parse_record(key[-1], data)
1873
yield key, content, digest
1875
def _read_records_iter_raw(self, records):
1876
"""Read text records from data file and yield raw data.
1878
This unpacks enough of the text record to validate the id is
1879
as expected but thats all.
1881
Each item the iterator yields is (key, bytes,
1882
expected_sha1_of_full_text).
1884
for key, data in self._read_records_iter_unchecked(records):
1885
# validate the header (note that we can only use the suffix in
1886
# current knit records).
1887
df, rec = self._parse_record_header(key, data)
1889
yield key, data, rec[3]
1891
def _read_records_iter_unchecked(self, records):
1892
"""Read text records from data file and yield raw data.
1894
No validation is done.
1896
Yields tuples of (key, data).
1898
# setup an iterator of the external records:
1899
# uses readv so nice and fast we hope.
1901
# grab the disk data needed.
1902
needed_offsets = [index_memo for key, index_memo
1904
raw_records = self._access.get_raw_records(needed_offsets)
1906
for key, index_memo in records:
1907
data = raw_records.next()
1910
def _record_to_data(self, key, digest, lines, dense_lines=None):
1911
"""Convert key, digest, lines into a raw data block.
1913
:param key: The key of the record. Currently keys are always serialised
1914
using just the trailing component.
1915
:param dense_lines: The bytes of lines but in a denser form. For
1916
instance, if lines is a list of 1000 bytestrings each ending in \n,
1917
dense_lines may be a list with one line in it, containing all the
1918
1000's lines and their \n's. Using dense_lines if it is already
1919
known is a win because the string join to create bytes in this
1920
function spends less time resizing the final string.
1921
:return: (len, a StringIO instance with the raw data ready to read.)
1923
# Note: using a string copy here increases memory pressure with e.g.
1924
# ISO's, but it is about 3 seconds faster on a 1.2Ghz intel machine
1925
# when doing the initial commit of a mozilla tree. RBC 20070921
1926
bytes = ''.join(chain(
1927
["version %s %d %s\n" % (key[-1],
1930
dense_lines or lines,
1931
["end %s\n" % key[-1]]))
1932
if type(bytes) != str:
1933
raise AssertionError(
1934
'data must be plain bytes was %s' % type(bytes))
1935
if lines and lines[-1][-1] != '\n':
1936
raise ValueError('corrupt lines value %r' % lines)
1937
compressed_bytes = tuned_gzip.bytes_to_gzip(bytes)
1938
return len(compressed_bytes), compressed_bytes
1940
def _split_header(self, line):
1943
raise KnitCorrupt(self,
1944
'unexpected number of elements in record header')
1948
"""See VersionedFiles.keys."""
1949
if 'evil' in debug.debug_flags:
1950
trace.mutter_callsite(2, "keys scales with size of history")
1951
sources = [self._index] + self._fallback_vfs
1953
for source in sources:
1954
result.update(source.keys())
1958
class _ContentMapGenerator(object):
1959
"""Generate texts or expose raw deltas for a set of texts."""
1961
def _get_content(self, key):
1962
"""Get the content object for key."""
1963
# Note that _get_content is only called when the _ContentMapGenerator
1964
# has been constructed with just one key requested for reconstruction.
1965
if key in self.nonlocal_keys:
1966
record = self.get_record_stream().next()
1967
# Create a content object on the fly
1968
lines = osutils.chunks_to_lines(record.get_bytes_as('chunked'))
1969
return PlainKnitContent(lines, record.key)
1354
options.append('fulltext')
1355
# isinstance is slower and we have no hierarchy.
1356
if self.factory.__class__ == KnitPlainFactory:
1357
# Use the already joined bytes saving iteration time in
1359
size, bytes = self._data._record_to_data(version_id, digest,
1360
lines, [line_bytes])
1362
# get mixed annotation + content and feed it into the
1364
store_lines = self.factory.lower_fulltext(content)
1365
size, bytes = self._data._record_to_data(version_id, digest,
1368
access_memo = self._data.add_raw_records([size], bytes)[0]
1369
self._index.add_versions(
1370
((version_id, options, access_memo, parents),),
1371
random_id=random_id)
1372
return digest, text_length, content
1374
def check(self, progress_bar=None):
1375
"""See VersionedFile.check()."""
1376
# This doesn't actually test extraction of everything, but that will
1377
# impact 'bzr check' substantially, and needs to be integrated with
1378
# care. However, it does check for the obvious problem of a delta with
1380
versions = self.versions()
1381
parent_map = self.get_parent_map(versions)
1382
for version in versions:
1383
if self._index.get_method(version) != 'fulltext':
1384
compression_parent = parent_map[version][0]
1385
if compression_parent not in parent_map:
1386
raise errors.KnitCorrupt(self,
1387
"Missing basis parent %s for %s" % (
1388
compression_parent, version))
1390
def get_lines(self, version_id):
1391
"""See VersionedFile.get_lines()."""
1392
return self.get_line_list([version_id])[0]
1394
def _get_record_map(self, version_ids):
1395
"""Produce a dictionary of knit records.
1397
:return: {version_id:(record, record_details, digest, next)}
1399
data returned from read_records
1401
opaque information to pass to parse_record
1403
SHA1 digest of the full text after all steps are done
1405
build-parent of the version, i.e. the leftmost ancestor.
1406
Will be None if the record is not a delta.
1408
position_map = self._get_components_positions(version_ids)
1409
# c = component_id, r = record_details, i_m = index_memo, n = next
1410
records = [(c, i_m) for c, (r, i_m, n)
1411
in position_map.iteritems()]
1413
for component_id, record, digest in \
1414
self._data.read_records_iter(records):
1415
(record_details, index_memo, next) = position_map[component_id]
1416
record_map[component_id] = record, record_details, digest, next
1420
def get_text(self, version_id):
1421
"""See VersionedFile.get_text"""
1422
return self.get_texts([version_id])[0]
1424
def get_texts(self, version_ids):
1425
return [''.join(l) for l in self.get_line_list(version_ids)]
1427
def get_line_list(self, version_ids):
1428
"""Return the texts of listed versions as a list of strings."""
1429
for version_id in version_ids:
1430
self.check_not_reserved_id(version_id)
1431
text_map, content_map = self._get_content_maps(version_ids)
1432
return [text_map[v] for v in version_ids]
1434
_get_lf_split_line_list = get_line_list
1436
def _get_content_maps(self, version_ids):
1437
"""Produce maps of text and KnitContents
1971
# local keys we can ask for directly
1972
return self._get_one_work(key)
1974
def get_record_stream(self):
1975
"""Get a record stream for the keys requested during __init__."""
1976
for record in self._work():
1980
"""Produce maps of text and KnitContents as dicts.
1439
1982
:return: (text_map, content_map) where text_map contains the texts for
1440
the requested versions and content_map contains the KnitContents.
1441
Both dicts take version_ids as their keys.
1983
the requested versions and content_map contains the KnitContents.
1985
# NB: By definition we never need to read remote sources unless texts
1986
# are requested from them: we don't delta across stores - and we
1987
# explicitly do not want to to prevent data loss situations.
1988
if self.global_map is None:
1989
self.global_map = self.vf.get_parent_map(self.keys)
1990
nonlocal_keys = self.nonlocal_keys
1992
missing_keys = set(nonlocal_keys)
1993
# Read from remote versioned file instances and provide to our caller.
1994
for source in self.vf._fallback_vfs:
1995
if not missing_keys:
1997
# Loop over fallback repositories asking them for texts - ignore
1998
# any missing from a particular fallback.
1999
for record in source.get_record_stream(missing_keys,
2001
if record.storage_kind == 'absent':
2002
# Not in thie particular stream, may be in one of the
2003
# other fallback vfs objects.
2005
missing_keys.remove(record.key)
2008
if self._raw_record_map is None:
2009
raise AssertionError('_raw_record_map should have been filled')
2011
for key in self.keys:
2012
if key in self.nonlocal_keys:
2014
yield LazyKnitContentFactory(key, self.global_map[key], self, first)
2017
def _get_one_work(self, requested_key):
2018
# Now, if we have calculated everything already, just return the
2020
if requested_key in self._contents_map:
2021
return self._contents_map[requested_key]
2022
# To simplify things, parse everything at once - code that wants one text
2023
# probably wants them all.
1443
2024
# FUTURE: This function could be improved for the 'extract many' case
1444
2025
# by tracking each component and only doing the copy when the number of
1445
2026
# children than need to apply delta's to it is > 1 or it is part of the
1446
2027
# final output.
1447
version_ids = list(version_ids)
1448
multiple_versions = len(version_ids) != 1
1449
record_map = self._get_record_map(version_ids)
1454
for version_id in version_ids:
2028
multiple_versions = len(self.keys) != 1
2029
if self._record_map is None:
2030
self._record_map = self.vf._raw_map_to_record_map(
2031
self._raw_record_map)
2032
record_map = self._record_map
2033
# raw_record_map is key:
2034
# Have read and parsed records at this point.
2035
for key in self.keys:
2036
if key in self.nonlocal_keys:
1455
2039
components = []
1457
2041
while cursor is not None:
1458
record, record_details, digest, next = record_map[cursor]
2043
record, record_details, digest, next = record_map[cursor]
2045
raise RevisionNotPresent(cursor, self)
1459
2046
components.append((cursor, record, record_details, digest))
1460
if cursor in content_map:
2048
if cursor in self._contents_map:
2049
# no need to plan further back
2050
components.append((cursor, None, None, None))
1465
2054
for (component_id, record, record_details,
1466
2055
digest) in reversed(components):
1467
if component_id in content_map:
1468
content = content_map[component_id]
2056
if component_id in self._contents_map:
2057
content = self._contents_map[component_id]
1470
content, delta = self.factory.parse_record(version_id,
2059
content, delta = self._factory.parse_record(key[-1],
1471
2060
record, record_details, content,
1472
2061
copy_base_content=multiple_versions)
1473
2062
if multiple_versions:
1474
content_map[component_id] = content
1476
content.cleanup_eol(copy_on_mutate=multiple_versions)
1477
final_content[version_id] = content
2063
self._contents_map[component_id] = content
1479
2065
# digest here is the digest from the last applied component.
1480
2066
text = content.text()
1481
2067
actual_sha = sha_strings(text)
1482
2068
if actual_sha != digest:
1483
raise KnitCorrupt(self.filename,
1485
'\n of reconstructed text does not match'
1487
'\n for version %s' %
1488
(actual_sha, digest, version_id))
1489
text_map[version_id] = text
1490
return text_map, final_content
1492
def iter_lines_added_or_present_in_versions(self, version_ids=None,
1494
"""See VersionedFile.iter_lines_added_or_present_in_versions()."""
1495
if version_ids is None:
1496
version_ids = self.versions()
1498
pb = progress.DummyProgress()
1499
# we don't care about inclusions, the caller cares.
1500
# but we need to setup a list of records to visit.
1501
# we need version_id, position, length
1502
version_id_records = []
1503
requested_versions = set(version_ids)
1504
# filter for available versions
1505
for version_id in requested_versions:
1506
if not self.has_version(version_id):
1507
raise RevisionNotPresent(version_id, self.filename)
1508
# get a in-component-order queue:
1509
for version_id in self.versions():
1510
if version_id in requested_versions:
1511
index_memo = self._index.get_position(version_id)
1512
version_id_records.append((version_id, index_memo))
1514
total = len(version_id_records)
1515
for version_idx, (version_id, data, sha_value) in \
1516
enumerate(self._data.read_records_iter(version_id_records)):
1517
pb.update('Walking content.', version_idx, total)
1518
method = self._index.get_method(version_id)
1519
if method == 'fulltext':
1520
line_iterator = self.factory.get_fulltext_content(data)
1521
elif method == 'line-delta':
1522
line_iterator = self.factory.get_linedelta_content(data)
1524
raise ValueError('invalid method %r' % (method,))
1525
# XXX: It might be more efficient to yield (version_id,
1526
# line_iterator) in the future. However for now, this is a simpler
1527
# change to integrate into the rest of the codebase. RBC 20071110
1528
for line in line_iterator:
1529
yield line, version_id
1531
pb.update('Walking content.', total, total)
1533
def num_versions(self):
1534
"""See VersionedFile.num_versions()."""
1535
return self._index.num_versions()
1537
__len__ = num_versions
1539
def annotate(self, version_id):
1540
"""See VersionedFile.annotate."""
1541
return self.factory.annotate(self, version_id)
1543
def get_parent_map(self, version_ids):
1544
"""See VersionedFile.get_parent_map."""
1545
return self._index.get_parent_map(version_ids)
1547
def get_ancestry(self, versions, topo_sorted=True):
1548
"""See VersionedFile.get_ancestry."""
1549
if isinstance(versions, basestring):
1550
versions = [versions]
1553
return self._index.get_ancestry(versions, topo_sorted)
1555
def get_ancestry_with_ghosts(self, versions):
1556
"""See VersionedFile.get_ancestry_with_ghosts."""
1557
if isinstance(versions, basestring):
1558
versions = [versions]
1561
return self._index.get_ancestry_with_ghosts(versions)
1563
def plan_merge(self, ver_a, ver_b):
1564
"""See VersionedFile.plan_merge."""
1565
ancestors_b = set(self.get_ancestry(ver_b, topo_sorted=False))
1566
ancestors_a = set(self.get_ancestry(ver_a, topo_sorted=False))
1567
annotated_a = self.annotate(ver_a)
1568
annotated_b = self.annotate(ver_b)
1569
return merge._plan_annotate_merge(annotated_a, annotated_b,
1570
ancestors_a, ancestors_b)
1573
class _KnitComponentFile(object):
1574
"""One of the files used to implement a knit database"""
1576
def __init__(self, transport, filename, mode, file_mode=None,
1577
create_parent_dir=False, dir_mode=None):
1578
self._transport = transport
1579
self._filename = filename
1581
self._file_mode = file_mode
1582
self._dir_mode = dir_mode
1583
self._create_parent_dir = create_parent_dir
1584
self._need_to_create = False
1586
def _full_path(self):
1587
"""Return the full path to this file."""
1588
return self._transport.base + self._filename
1590
def check_header(self, fp):
1591
line = fp.readline()
1593
# An empty file can actually be treated as though the file doesn't
1595
raise errors.NoSuchFile(self._full_path())
1596
if line != self.HEADER:
1597
raise KnitHeaderError(badline=line,
1598
filename=self._transport.abspath(self._filename))
1601
return '%s(%s)' % (self.__class__.__name__, self._filename)
1604
class _KnitIndex(_KnitComponentFile):
1605
"""Manages knit index file.
1607
The index is already kept in memory and read on startup, to enable
2069
raise SHA1KnitCorrupt(self, actual_sha, digest, key, text)
2070
if multiple_versions:
2071
return self._contents_map[requested_key]
2075
def _wire_bytes(self):
2076
"""Get the bytes to put on the wire for 'key'.
2078
The first collection of bytes asked for returns the serialised
2079
raw_record_map and the additional details (key, parent) for key.
2080
Subsequent calls return just the additional details (key, parent).
2081
The wire storage_kind given for the first key is 'knit-delta-closure',
2082
For subsequent keys it is 'knit-delta-closure-ref'.
2084
:param key: A key from the content generator.
2085
:return: Bytes to put on the wire.
2088
# kind marker for dispatch on the far side,
2089
lines.append('knit-delta-closure')
2091
if self.vf._factory.annotated:
2092
lines.append('annotated')
2095
# then the list of keys
2096
lines.append('\t'.join(['\x00'.join(key) for key in self.keys
2097
if key not in self.nonlocal_keys]))
2098
# then the _raw_record_map in serialised form:
2100
# for each item in the map:
2102
# 1 line with parents if the key is to be yielded (None: for None, '' for ())
2103
# one line with method
2104
# one line with noeol
2105
# one line with next ('' for None)
2106
# one line with byte count of the record bytes
2108
for key, (record_bytes, (method, noeol), next) in \
2109
self._raw_record_map.iteritems():
2110
key_bytes = '\x00'.join(key)
2111
parents = self.global_map.get(key, None)
2113
parent_bytes = 'None:'
2115
parent_bytes = '\t'.join('\x00'.join(key) for key in parents)
2116
method_bytes = method
2122
next_bytes = '\x00'.join(next)
2125
map_byte_list.append('%s\n%s\n%s\n%s\n%s\n%d\n%s' % (
2126
key_bytes, parent_bytes, method_bytes, noeol_bytes, next_bytes,
2127
len(record_bytes), record_bytes))
2128
map_bytes = ''.join(map_byte_list)
2129
lines.append(map_bytes)
2130
bytes = '\n'.join(lines)
2134
class _VFContentMapGenerator(_ContentMapGenerator):
2135
"""Content map generator reading from a VersionedFiles object."""
2137
def __init__(self, versioned_files, keys, nonlocal_keys=None,
2138
global_map=None, raw_record_map=None):
2139
"""Create a _ContentMapGenerator.
2141
:param versioned_files: The versioned files that the texts are being
2143
:param keys: The keys to produce content maps for.
2144
:param nonlocal_keys: An iterable of keys(possibly intersecting keys)
2145
which are known to not be in this knit, but rather in one of the
2147
:param global_map: The result of get_parent_map(keys) (or a supermap).
2148
This is required if get_record_stream() is to be used.
2149
:param raw_record_map: A unparsed raw record map to use for answering
2152
# The vf to source data from
2153
self.vf = versioned_files
2155
self.keys = list(keys)
2156
# Keys known to be in fallback vfs objects
2157
if nonlocal_keys is None:
2158
self.nonlocal_keys = set()
2160
self.nonlocal_keys = frozenset(nonlocal_keys)
2161
# Parents data for keys to be returned in get_record_stream
2162
self.global_map = global_map
2163
# The chunked lists for self.keys in text form
2165
# A cache of KnitContent objects used in extracting texts.
2166
self._contents_map = {}
2167
# All the knit records needed to assemble the requested keys as full
2169
self._record_map = None
2170
if raw_record_map is None:
2171
self._raw_record_map = self.vf._get_record_map_unparsed(keys,
2174
self._raw_record_map = raw_record_map
2175
# the factory for parsing records
2176
self._factory = self.vf._factory
2179
class _NetworkContentMapGenerator(_ContentMapGenerator):
2180
"""Content map generator sourced from a network stream."""
2182
def __init__(self, bytes, line_end):
2183
"""Construct a _NetworkContentMapGenerator from a bytes block."""
2185
self.global_map = {}
2186
self._raw_record_map = {}
2187
self._contents_map = {}
2188
self._record_map = None
2189
self.nonlocal_keys = []
2190
# Get access to record parsing facilities
2191
self.vf = KnitVersionedFiles(None, None)
2194
line_end = bytes.find('\n', start)
2195
line = bytes[start:line_end]
2196
start = line_end + 1
2197
if line == 'annotated':
2198
self._factory = KnitAnnotateFactory()
2200
self._factory = KnitPlainFactory()
2201
# list of keys to emit in get_record_stream
2202
line_end = bytes.find('\n', start)
2203
line = bytes[start:line_end]
2204
start = line_end + 1
2206
tuple(segment.split('\x00')) for segment in line.split('\t')
2208
# now a loop until the end. XXX: It would be nice if this was just a
2209
# bunch of the same records as get_record_stream(..., False) gives, but
2210
# there is a decent sized gap stopping that at the moment.
2214
line_end = bytes.find('\n', start)
2215
key = tuple(bytes[start:line_end].split('\x00'))
2216
start = line_end + 1
2217
# 1 line with parents (None: for None, '' for ())
2218
line_end = bytes.find('\n', start)
2219
line = bytes[start:line_end]
2224
[tuple(segment.split('\x00')) for segment in line.split('\t')
2226
self.global_map[key] = parents
2227
start = line_end + 1
2228
# one line with method
2229
line_end = bytes.find('\n', start)
2230
line = bytes[start:line_end]
2232
start = line_end + 1
2233
# one line with noeol
2234
line_end = bytes.find('\n', start)
2235
line = bytes[start:line_end]
2237
start = line_end + 1
2238
# one line with next ('' for None)
2239
line_end = bytes.find('\n', start)
2240
line = bytes[start:line_end]
2244
next = tuple(bytes[start:line_end].split('\x00'))
2245
start = line_end + 1
2246
# one line with byte count of the record bytes
2247
line_end = bytes.find('\n', start)
2248
line = bytes[start:line_end]
2250
start = line_end + 1
2252
record_bytes = bytes[start:start+count]
2253
start = start + count
2255
self._raw_record_map[key] = (record_bytes, (method, noeol), next)
2257
def get_record_stream(self):
2258
"""Get a record stream for for keys requested by the bytestream."""
2260
for key in self.keys:
2261
yield LazyKnitContentFactory(key, self.global_map[key], self, first)
2264
def _wire_bytes(self):
2268
class _KndxIndex(object):
2269
"""Manages knit index files
2271
The index is kept in memory and read on startup, to enable
1608
2272
fast lookups of revision information. The cursor of the index
1609
2273
file is always pointing to the end, making it easy to append
1652
2316
to ensure that records always start on new lines even if the last write was
1653
2317
interrupted. As a result its normal for the last line in the index to be
1654
2318
missing a trailing newline. One can be added with no harmful effects.
2320
:ivar _kndx_cache: dict from prefix to the old state of KnitIndex objects,
2321
where prefix is e.g. the (fileid,) for .texts instances or () for
2322
constant-mapped things like .revisions, and the old state is
2323
tuple(history_vector, cache_dict). This is used to prevent having an
2324
ABI change with the C extension that reads .kndx files.
1657
2327
HEADER = "# bzr knit index 8\n"
1659
# speed of knit parsing went from 280 ms to 280 ms with slots addition.
1660
# __slots__ = ['_cache', '_history', '_transport', '_filename']
1662
def _cache_version(self, version_id, options, pos, size, parents):
2329
def __init__(self, transport, mapper, get_scope, allow_writes, is_locked):
2330
"""Create a _KndxIndex on transport using mapper."""
2331
self._transport = transport
2332
self._mapper = mapper
2333
self._get_scope = get_scope
2334
self._allow_writes = allow_writes
2335
self._is_locked = is_locked
2337
self.has_graph = True
2339
def add_records(self, records, random_id=False, missing_compression_parents=False):
2340
"""Add multiple records to the index.
2342
:param records: a list of tuples:
2343
(key, options, access_memo, parents).
2344
:param random_id: If True the ids being added were randomly generated
2345
and no check for existence will be performed.
2346
:param missing_compression_parents: If True the records being added are
2347
only compressed against texts already in the index (or inside
2348
records). If False the records all refer to unavailable texts (or
2349
texts inside records) as compression parents.
2351
if missing_compression_parents:
2352
# It might be nice to get the edge of the records. But keys isn't
2354
keys = sorted(record[0] for record in records)
2355
raise errors.RevisionNotPresent(keys, self)
2357
for record in records:
2360
path = self._mapper.map(key) + '.kndx'
2361
path_keys = paths.setdefault(path, (prefix, []))
2362
path_keys[1].append(record)
2363
for path in sorted(paths):
2364
prefix, path_keys = paths[path]
2365
self._load_prefixes([prefix])
2367
orig_history = self._kndx_cache[prefix][1][:]
2368
orig_cache = self._kndx_cache[prefix][0].copy()
2371
for key, options, (_, pos, size), parents in path_keys:
2373
# kndx indices cannot be parentless.
2375
line = "\n%s %s %s %s %s :" % (
2376
key[-1], ','.join(options), pos, size,
2377
self._dictionary_compress(parents))
2378
if type(line) != str:
2379
raise AssertionError(
2380
'data must be utf8 was %s' % type(line))
2382
self._cache_key(key, options, pos, size, parents)
2383
if len(orig_history):
2384
self._transport.append_bytes(path, ''.join(lines))
2386
self._init_index(path, lines)
2388
# If any problems happen, restore the original values and re-raise
2389
self._kndx_cache[prefix] = (orig_cache, orig_history)
2392
def scan_unvalidated_index(self, graph_index):
2393
"""See _KnitGraphIndex.scan_unvalidated_index."""
2394
# Because kndx files do not support atomic insertion via separate index
2395
# files, they do not support this method.
2396
raise NotImplementedError(self.scan_unvalidated_index)
2398
def get_missing_compression_parents(self):
2399
"""See _KnitGraphIndex.get_missing_compression_parents."""
2400
# Because kndx files do not support atomic insertion via separate index
2401
# files, they do not support this method.
2402
raise NotImplementedError(self.get_missing_compression_parents)
2404
def _cache_key(self, key, options, pos, size, parent_keys):
1663
2405
"""Cache a version record in the history array and index cache.
1665
2407
This is inlined into _load_data for performance. KEEP IN SYNC.
1666
2408
(It saves 60ms, 25% of the __init__ overhead on local 4000 record
2412
version_id = key[-1]
2413
# last-element only for compatibilty with the C load_data.
2414
parents = tuple(parent[-1] for parent in parent_keys)
2415
for parent in parent_keys:
2416
if parent[:-1] != prefix:
2417
raise ValueError("mismatched prefixes for %r, %r" % (
2419
cache, history = self._kndx_cache[prefix]
1669
2420
# only want the _history index to reference the 1st index entry
1670
2421
# for version_id
1671
if version_id not in self._cache:
1672
index = len(self._history)
1673
self._history.append(version_id)
2422
if version_id not in cache:
2423
index = len(history)
2424
history.append(version_id)
1675
index = self._cache[version_id][5]
1676
self._cache[version_id] = (version_id,
2426
index = cache[version_id][5]
2427
cache[version_id] = (version_id,
2434
def check_header(self, fp):
2435
line = fp.readline()
2437
# An empty file can actually be treated as though the file doesn't
2439
raise errors.NoSuchFile(self)
2440
if line != self.HEADER:
2441
raise KnitHeaderError(badline=line, filename=self)
2443
def _check_read(self):
2444
if not self._is_locked():
2445
raise errors.ObjectNotLocked(self)
2446
if self._get_scope() != self._scope:
1683
2449
def _check_write_ok(self):
2450
"""Assert if not writes are permitted."""
2451
if not self._is_locked():
2452
raise errors.ObjectNotLocked(self)
1684
2453
if self._get_scope() != self._scope:
1685
raise errors.OutSideTransaction()
1686
2455
if self._mode != 'w':
1687
2456
raise errors.ReadOnlyObjectDirtiedError(self)
1689
def __init__(self, transport, filename, mode, create=False, file_mode=None,
1690
create_parent_dir=False, delay_create=False, dir_mode=None,
1692
_KnitComponentFile.__init__(self, transport, filename, mode,
1693
file_mode=file_mode,
1694
create_parent_dir=create_parent_dir,
1697
# position in _history is the 'official' index for a revision
1698
# but the values may have come from a newer entry.
1699
# so - wc -l of a knit index is != the number of unique names
1703
fp = self._transport.get(self._filename)
1705
# _load_data may raise NoSuchFile if the target knit is
1707
_load_data(self, fp)
1711
if mode != 'w' or not create:
1714
self._need_to_create = True
1716
self._transport.put_bytes_non_atomic(
1717
self._filename, self.HEADER, mode=self._file_mode)
1718
self._scope = get_scope()
1719
self._get_scope = get_scope
1721
def get_ancestry(self, versions, topo_sorted=True):
1722
"""See VersionedFile.get_ancestry."""
1723
# get a graph of all the mentioned versions:
1725
pending = set(versions)
1728
version = pending.pop()
1731
parents = [p for p in cache[version][4] if p in cache]
1733
raise RevisionNotPresent(version, self._filename)
1734
# if not completed and not a ghost
1735
pending.update([p for p in parents if p not in graph])
1736
graph[version] = parents
1739
return topo_sort(graph.items())
1741
def get_ancestry_with_ghosts(self, versions):
1742
"""See VersionedFile.get_ancestry_with_ghosts."""
1743
# get a graph of all the mentioned versions:
1744
self.check_versions_present(versions)
1747
pending = set(versions)
1749
version = pending.pop()
1751
parents = cache[version][4]
1757
pending.update([p for p in parents if p not in graph])
1758
graph[version] = parents
1759
return topo_sort(graph.items())
1761
def get_build_details(self, version_ids):
1762
"""Get the method, index_memo and compression parent for version_ids.
2458
def get_build_details(self, keys):
2459
"""Get the method, index_memo and compression parent for keys.
1764
2461
Ghosts are omitted from the result.
1766
:param version_ids: An iterable of version_ids.
1767
:return: A dict of version_id:(index_memo, compression_parent,
1768
parents, record_details).
2463
:param keys: An iterable of keys.
2464
:return: A dict of key:(index_memo, compression_parent, parents,
1770
2467
opaque structure to pass to read_records to extract the raw
1777
2474
extra information about the content which needs to be passed to
1778
2475
Factory.parse_record
2477
parent_map = self.get_parent_map(keys)
1781
for version_id in version_ids:
1782
if version_id not in self._cache:
1783
# ghosts are omitted
1785
method = self.get_method(version_id)
1786
parents = self.get_parents_with_ghosts(version_id)
2480
if key not in parent_map:
2482
method = self.get_method(key)
2483
parents = parent_map[key]
1787
2484
if method == 'fulltext':
1788
2485
compression_parent = None
1790
2487
compression_parent = parents[0]
1791
noeol = 'no-eol' in self.get_options(version_id)
1792
index_memo = self.get_position(version_id)
1793
result[version_id] = (index_memo, compression_parent,
2488
noeol = 'no-eol' in self.get_options(key)
2489
index_memo = self.get_position(key)
2490
result[key] = (index_memo, compression_parent,
1794
2491
parents, (method, noeol))
1797
def num_versions(self):
1798
return len(self._history)
1800
__len__ = num_versions
1802
def get_versions(self):
1803
"""Get all the versions in the file. not topologically sorted."""
1804
return self._history
1806
def _version_list_to_index(self, versions):
2494
def get_method(self, key):
2495
"""Return compression method of specified key."""
2496
options = self.get_options(key)
2497
if 'fulltext' in options:
2499
elif 'line-delta' in options:
2502
raise errors.KnitIndexUnknownMethod(self, options)
2504
def get_options(self, key):
2505
"""Return a list representing options.
2509
prefix, suffix = self._split_key(key)
2510
self._load_prefixes([prefix])
2512
return self._kndx_cache[prefix][0][suffix][1]
2514
raise RevisionNotPresent(key, self)
2516
def get_parent_map(self, keys):
2517
"""Get a map of the parents of keys.
2519
:param keys: The keys to look up parents for.
2520
:return: A mapping from keys to parents. Absent keys are absent from
2523
# Parse what we need to up front, this potentially trades off I/O
2524
# locality (.kndx and .knit in the same block group for the same file
2525
# id) for less checking in inner loops.
2526
prefixes = set(key[:-1] for key in keys)
2527
self._load_prefixes(prefixes)
2532
suffix_parents = self._kndx_cache[prefix][0][key[-1]][4]
2536
result[key] = tuple(prefix + (suffix,) for
2537
suffix in suffix_parents)
2540
def get_position(self, key):
2541
"""Return details needed to access the version.
2543
:return: a tuple (key, data position, size) to hand to the access
2544
logic to get the record.
2546
prefix, suffix = self._split_key(key)
2547
self._load_prefixes([prefix])
2548
entry = self._kndx_cache[prefix][0][suffix]
2549
return key, entry[2], entry[3]
2551
has_key = _mod_index._has_key_from_parent_map
2553
def _init_index(self, path, extra_lines=[]):
2554
"""Initialize an index."""
2556
sio.write(self.HEADER)
2557
sio.writelines(extra_lines)
2559
self._transport.put_file_non_atomic(path, sio,
2560
create_parent_dir=True)
2561
# self._create_parent_dir)
2562
# mode=self._file_mode,
2563
# dir_mode=self._dir_mode)
2566
"""Get all the keys in the collection.
2568
The keys are not ordered.
2571
# Identify all key prefixes.
2572
# XXX: A bit hacky, needs polish.
2573
if type(self._mapper) == ConstantMapper:
2577
for quoted_relpath in self._transport.iter_files_recursive():
2578
path, ext = os.path.splitext(quoted_relpath)
2580
prefixes = [self._mapper.unmap(path) for path in relpaths]
2581
self._load_prefixes(prefixes)
2582
for prefix in prefixes:
2583
for suffix in self._kndx_cache[prefix][1]:
2584
result.add(prefix + (suffix,))
2587
def _load_prefixes(self, prefixes):
2588
"""Load the indices for prefixes."""
2590
for prefix in prefixes:
2591
if prefix not in self._kndx_cache:
2592
# the load_data interface writes to these variables.
2595
self._filename = prefix
2597
path = self._mapper.map(prefix) + '.kndx'
2598
fp = self._transport.get(path)
2600
# _load_data may raise NoSuchFile if the target knit is
2602
_load_data(self, fp)
2605
self._kndx_cache[prefix] = (self._cache, self._history)
2610
self._kndx_cache[prefix] = ({}, [])
2611
if type(self._mapper) == ConstantMapper:
2612
# preserve behaviour for revisions.kndx etc.
2613
self._init_index(path)
2618
missing_keys = _mod_index._missing_keys_from_parent_map
2620
def _partition_keys(self, keys):
2621
"""Turn keys into a dict of prefix:suffix_list."""
2624
prefix_keys = result.setdefault(key[:-1], [])
2625
prefix_keys.append(key[-1])
2628
def _dictionary_compress(self, keys):
2629
"""Dictionary compress keys.
2631
:param keys: The keys to generate references to.
2632
:return: A string representation of keys. keys which are present are
2633
dictionary compressed, and others are emitted as fulltext with a
1807
2638
result_list = []
1809
for version in versions:
1810
if version in cache:
2639
prefix = keys[0][:-1]
2640
cache = self._kndx_cache[prefix][0]
2642
if key[:-1] != prefix:
2643
# kndx indices cannot refer across partitioned storage.
2644
raise ValueError("mismatched prefixes for %r" % keys)
2645
if key[-1] in cache:
1811
2646
# -- inlined lookup() --
1812
result_list.append(str(cache[version][5]))
2647
result_list.append(str(cache[key[-1]][5]))
1813
2648
# -- end lookup () --
1815
result_list.append('.' + version)
2650
result_list.append('.' + key[-1])
1816
2651
return ' '.join(result_list)
1818
def add_version(self, version_id, options, index_memo, parents):
1819
"""Add a version record to the index."""
1820
self.add_versions(((version_id, options, index_memo, parents),))
1822
def add_versions(self, versions, random_id=False):
1823
"""Add multiple versions to the index.
1825
:param versions: a list of tuples:
1826
(version_id, options, pos, size, parents).
1827
:param random_id: If True the ids being added were randomly generated
1828
and no check for existence will be performed.
1831
orig_history = self._history[:]
1832
orig_cache = self._cache.copy()
1835
for version_id, options, (index, pos, size), parents in versions:
1836
line = "\n%s %s %s %s %s :" % (version_id,
1840
self._version_list_to_index(parents))
1842
self._cache_version(version_id, options, pos, size, tuple(parents))
1843
if not self._need_to_create:
1844
self._transport.append_bytes(self._filename, ''.join(lines))
1847
sio.write(self.HEADER)
1848
sio.writelines(lines)
1850
self._transport.put_file_non_atomic(self._filename, sio,
1851
create_parent_dir=self._create_parent_dir,
1852
mode=self._file_mode,
1853
dir_mode=self._dir_mode)
1854
self._need_to_create = False
1856
# If any problems happen, restore the original values and re-raise
1857
self._history = orig_history
1858
self._cache = orig_cache
1861
def has_version(self, version_id):
1862
"""True if the version is in the index."""
1863
return version_id in self._cache
1865
def get_position(self, version_id):
1866
"""Return details needed to access the version.
1868
.kndx indices do not support split-out data, so return None for the
1871
:return: a tuple (None, data position, size) to hand to the access
1872
logic to get the record.
1874
entry = self._cache[version_id]
1875
return None, entry[2], entry[3]
1877
def get_method(self, version_id):
1878
"""Return compression method of specified version."""
1880
options = self._cache[version_id][1]
1882
raise RevisionNotPresent(version_id, self._filename)
1883
if 'fulltext' in options:
2653
def _reset_cache(self):
2654
# Possibly this should be a LRU cache. A dictionary from key_prefix to
2655
# (cache_dict, history_vector) for parsed kndx files.
2656
self._kndx_cache = {}
2657
self._scope = self._get_scope()
2658
allow_writes = self._allow_writes()
1886
if 'line-delta' not in options:
1887
raise errors.KnitIndexUnknownMethod(self._full_path(), options)
1890
def get_options(self, version_id):
1891
"""Return a list representing options.
2664
def _sort_keys_by_io(self, keys, positions):
2665
"""Figure out an optimal order to read the records for the given keys.
2667
Sort keys, grouped by index and sorted by position.
2669
:param keys: A list of keys whose records we want to read. This will be
2671
:param positions: A dict, such as the one returned by
2672
_get_components_positions()
1895
return self._cache[version_id][1]
1897
def get_parent_map(self, version_ids):
1898
"""Passed through to by KnitVersionedFile.get_parent_map."""
1900
for version_id in version_ids:
2675
def get_sort_key(key):
2676
index_memo = positions[key][1]
2677
# Group by prefix and position. index_memo[0] is the key, so it is
2678
# (file_id, revision_id) and we don't want to sort on revision_id,
2679
# index_memo[1] is the position, and index_memo[2] is the size,
2680
# which doesn't matter for the sort
2681
return index_memo[0][:-1], index_memo[1]
2682
return keys.sort(key=get_sort_key)
2684
_get_total_build_size = _get_total_build_size
2686
def _split_key(self, key):
2687
"""Split key into a prefix and suffix."""
2688
return key[:-1], key[-1]
2691
class _KeyRefs(object):
2694
# dict mapping 'key' to 'set of keys referring to that key'
2697
def add_references(self, key, refs):
2698
# Record the new references
2699
for referenced in refs:
1902
result[version_id] = tuple(self._cache[version_id][4])
2701
needed_by = self.refs[referenced]
1903
2702
except KeyError:
2703
needed_by = self.refs[referenced] = set()
2705
# Discard references satisfied by the new key
2708
def get_unsatisfied_refs(self):
2709
return self.refs.iterkeys()
2711
def add_key(self, key):
2715
# No keys depended on this key. That's ok.
2718
def add_keys(self, keys):
2722
def get_referrers(self):
2724
for referrers in self.refs.itervalues():
2725
result.update(referrers)
1907
def get_parents_with_ghosts(self, version_id):
1908
"""Return parents of specified version with ghosts."""
1910
return self.get_parent_map([version_id])[version_id]
1912
raise RevisionNotPresent(version_id, self)
1914
def check_versions_present(self, version_ids):
1915
"""Check that all specified versions are present."""
1917
for version_id in version_ids:
1918
if version_id not in cache:
1919
raise RevisionNotPresent(version_id, self._filename)
1922
class KnitGraphIndex(object):
1923
"""A knit index that builds on GraphIndex."""
1925
def __init__(self, graph_index, deltas=False, parents=True, add_callback=None):
2729
class _KnitGraphIndex(object):
2730
"""A KnitVersionedFiles index layered on GraphIndex."""
2732
def __init__(self, graph_index, is_locked, deltas=False, parents=True,
2733
add_callback=None, track_external_parent_refs=False):
1926
2734
"""Construct a KnitGraphIndex on a graph_index.
1928
2736
:param graph_index: An implementation of bzrlib.index.GraphIndex.
2737
:param is_locked: A callback to check whether the object should answer
1929
2739
:param deltas: Allow delta-compressed records.
2740
:param parents: If True, record knits parents, if not do not record
1930
2742
:param add_callback: If not None, allow additions to the index and call
1931
2743
this callback with a list of added GraphIndex nodes:
1932
2744
[(node, value, node_refs), ...]
1933
:param parents: If True, record knits parents, if not do not record
2745
:param is_locked: A callback, returns True if the index is locked and
2747
:param track_external_parent_refs: If True, record all external parent
2748
references parents from added records. These can be retrieved
2749
later by calling get_missing_parents().
2751
self._add_callback = add_callback
1936
2752
self._graph_index = graph_index
1937
2753
self._deltas = deltas
1938
self._add_callback = add_callback
1939
2754
self._parents = parents
1940
2755
if deltas and not parents:
2756
# XXX: TODO: Delta tree and parent graph should be conceptually
1941
2758
raise KnitCorrupt(self, "Cannot do delta compression without "
1942
2759
"parent tracking.")
1944
def _check_write_ok(self):
1947
def _get_entries(self, keys, check_present=False):
1948
"""Get the entries for keys.
1950
:param keys: An iterable of index keys, - 1-tuples.
1955
for node in self._graph_index.iter_entries(keys):
1957
found_keys.add(node[1])
1959
# adapt parentless index to the rest of the code.
1960
for node in self._graph_index.iter_entries(keys):
1961
yield node[0], node[1], node[2], ()
1962
found_keys.add(node[1])
1964
missing_keys = keys.difference(found_keys)
1966
raise RevisionNotPresent(missing_keys.pop(), self)
1968
def _present_keys(self, version_ids):
1970
node[1] for node in self._get_entries(version_ids)])
1972
def _parentless_ancestry(self, versions):
1973
"""Honour the get_ancestry API for parentless knit indices."""
1974
wanted_keys = self._version_ids_to_keys(versions)
1975
present_keys = self._present_keys(wanted_keys)
1976
missing = set(wanted_keys).difference(present_keys)
1978
raise RevisionNotPresent(missing.pop(), self)
1979
return list(self._keys_to_version_ids(present_keys))
1981
def get_ancestry(self, versions, topo_sorted=True):
1982
"""See VersionedFile.get_ancestry."""
1983
if not self._parents:
1984
return self._parentless_ancestry(versions)
1985
# XXX: This will do len(history) index calls - perhaps
1986
# it should be altered to be a index core feature?
1987
# get a graph of all the mentioned versions:
1990
versions = self._version_ids_to_keys(versions)
1991
pending = set(versions)
1993
# get all pending nodes
1994
this_iteration = pending
1995
new_nodes = self._get_entries(this_iteration)
1998
for (index, key, value, node_refs) in new_nodes:
1999
# dont ask for ghosties - otherwise
2000
# we we can end up looping with pending
2001
# being entirely ghosted.
2002
graph[key] = [parent for parent in node_refs[0]
2003
if parent not in ghosts]
2005
for parent in graph[key]:
2006
# dont examine known nodes again
2011
ghosts.update(this_iteration.difference(found))
2012
if versions.difference(graph):
2013
raise RevisionNotPresent(versions.difference(graph).pop(), self)
2015
result_keys = topo_sort(graph.items())
2017
result_keys = graph.iterkeys()
2018
return [key[0] for key in result_keys]
2020
def get_ancestry_with_ghosts(self, versions):
2021
"""See VersionedFile.get_ancestry."""
2022
if not self._parents:
2023
return self._parentless_ancestry(versions)
2024
# XXX: This will do len(history) index calls - perhaps
2025
# it should be altered to be a index core feature?
2026
# get a graph of all the mentioned versions:
2028
versions = self._version_ids_to_keys(versions)
2029
pending = set(versions)
2031
# get all pending nodes
2032
this_iteration = pending
2033
new_nodes = self._get_entries(this_iteration)
2035
for (index, key, value, node_refs) in new_nodes:
2036
graph[key] = node_refs[0]
2038
for parent in graph[key]:
2039
# dont examine known nodes again
2043
missing_versions = this_iteration.difference(graph)
2044
missing_needed = versions.intersection(missing_versions)
2046
raise RevisionNotPresent(missing_needed.pop(), self)
2047
for missing_version in missing_versions:
2048
# add a key, no parents
2049
graph[missing_version] = []
2050
pending.discard(missing_version) # don't look for it
2051
result_keys = topo_sort(graph.items())
2052
return [key[0] for key in result_keys]
2054
def get_build_details(self, version_ids):
2055
"""Get the method, index_memo and compression parent for version_ids.
2057
Ghosts are omitted from the result.
2059
:param version_ids: An iterable of version_ids.
2060
:return: A dict of version_id:(index_memo, compression_parent,
2061
parents, record_details).
2063
opaque structure to pass to read_records to extract the raw
2066
Content that this record is built upon, may be None
2068
Logical parents of this node
2070
extra information about the content which needs to be passed to
2071
Factory.parse_record
2074
entries = self._get_entries(self._version_ids_to_keys(version_ids), True)
2075
for entry in entries:
2076
version_id = self._keys_to_version_ids((entry[1],))[0]
2077
if not self._parents:
2080
parents = self._keys_to_version_ids(entry[3][0])
2081
if not self._deltas:
2082
compression_parent = None
2084
compression_parent_key = self._compression_parent(entry)
2085
if compression_parent_key:
2086
compression_parent = self._keys_to_version_ids(
2087
(compression_parent_key,))[0]
2089
compression_parent = None
2090
noeol = (entry[2][0] == 'N')
2091
if compression_parent:
2092
method = 'line-delta'
2095
result[version_id] = (self._node_to_position(entry),
2096
compression_parent, parents,
2100
def _compression_parent(self, an_entry):
2101
# return the key that an_entry is compressed against, or None
2102
# Grab the second parent list (as deltas implies parents currently)
2103
compression_parents = an_entry[3][1]
2104
if not compression_parents:
2106
return compression_parents[0]
2108
def _get_method(self, node):
2109
if not self._deltas:
2111
if self._compression_parent(node):
2116
def num_versions(self):
2117
return len(list(self._graph_index.iter_all_entries()))
2119
__len__ = num_versions
2121
def get_versions(self):
2122
"""Get all the versions in the file. not topologically sorted."""
2123
return [node[1][0] for node in self._graph_index.iter_all_entries()]
2125
def has_version(self, version_id):
2126
"""True if the version is in the index."""
2127
return len(self._present_keys(self._version_ids_to_keys([version_id]))) == 1
2129
def _keys_to_version_ids(self, keys):
2130
return tuple(key[0] for key in keys)
2132
def get_position(self, version_id):
2133
"""Return details needed to access the version.
2135
:return: a tuple (index, data position, size) to hand to the access
2136
logic to get the record.
2138
node = self._get_node(version_id)
2139
return self._node_to_position(node)
2141
def _node_to_position(self, node):
2142
"""Convert an index value to position details."""
2143
bits = node[2][1:].split(' ')
2144
return node[0], int(bits[0]), int(bits[1])
2146
def get_method(self, version_id):
2147
"""Return compression method of specified version."""
2148
return self._get_method(self._get_node(version_id))
2150
def _get_node(self, version_id):
2152
return list(self._get_entries(self._version_ids_to_keys([version_id])))[0]
2154
raise RevisionNotPresent(version_id, self)
2156
def get_options(self, version_id):
2157
"""Return a list representing options.
2161
node = self._get_node(version_id)
2162
options = [self._get_method(node)]
2163
if node[2][0] == 'N':
2164
options.append('no-eol')
2167
def get_parent_map(self, version_ids):
2168
"""Passed through to by KnitVersionedFile.get_parent_map."""
2169
nodes = self._get_entries(self._version_ids_to_keys(version_ids))
2173
result[node[1][0]] = self._keys_to_version_ids(node[3][0])
2176
result[node[1][0]] = ()
2179
def get_parents_with_ghosts(self, version_id):
2180
"""Return parents of specified version with ghosts."""
2182
return self.get_parent_map([version_id])[version_id]
2184
raise RevisionNotPresent(version_id, self)
2186
def check_versions_present(self, version_ids):
2187
"""Check that all specified versions are present."""
2188
keys = self._version_ids_to_keys(version_ids)
2189
present = self._present_keys(keys)
2190
missing = keys.difference(present)
2192
raise RevisionNotPresent(missing.pop(), self)
2194
def add_version(self, version_id, options, access_memo, parents):
2195
"""Add a version record to the index."""
2196
return self.add_versions(((version_id, options, access_memo, parents),))
2198
def add_versions(self, versions, random_id=False):
2199
"""Add multiple versions to the index.
2760
self.has_graph = parents
2761
self._is_locked = is_locked
2762
self._missing_compression_parents = set()
2763
if track_external_parent_refs:
2764
self._key_dependencies = _KeyRefs()
2766
self._key_dependencies = None
2769
return "%s(%r)" % (self.__class__.__name__, self._graph_index)
2771
def add_records(self, records, random_id=False,
2772
missing_compression_parents=False):
2773
"""Add multiple records to the index.
2201
2775
This function does not insert data into the Immutable GraphIndex
2202
2776
backing the KnitGraphIndex, instead it prepares data for insertion by
2203
2777
the caller and checks that it is safe to insert then calls
2204
2778
self._add_callback with the prepared GraphIndex nodes.
2206
:param versions: a list of tuples:
2207
(version_id, options, pos, size, parents).
2780
:param records: a list of tuples:
2781
(key, options, access_memo, parents).
2208
2782
:param random_id: If True the ids being added were randomly generated
2209
2783
and no check for existence will be performed.
2784
:param missing_compression_parents: If True the records being added are
2785
only compressed against texts already in the index (or inside
2786
records). If False the records all refer to unavailable texts (or
2787
texts inside records) as compression parents.
2211
2789
if not self._add_callback:
2212
2790
raise errors.ReadOnlyError(self)
2213
2791
# we hope there are no repositories with inconsistent parentage
2218
for (version_id, options, access_memo, parents) in versions:
2795
compression_parents = set()
2796
key_dependencies = self._key_dependencies
2797
for (key, options, access_memo, parents) in records:
2799
parents = tuple(parents)
2800
if key_dependencies is not None:
2801
key_dependencies.add_references(key, parents)
2219
2802
index, pos, size = access_memo
2220
key = (version_id, )
2221
parents = tuple((parent, ) for parent in parents)
2222
2803
if 'no-eol' in options:
2256
2841
for key, (value, node_refs) in keys.iteritems():
2257
2842
result.append((key, value))
2258
2843
self._add_callback(result)
2260
def _version_ids_to_keys(self, version_ids):
2261
return set((version_id, ) for version_id in version_ids)
2264
class _KnitAccess(object):
2265
"""Access to knit records in a .knit file."""
2267
def __init__(self, transport, filename, _file_mode, _dir_mode,
2268
_need_to_create, _create_parent_dir):
2269
"""Create a _KnitAccess for accessing and inserting data.
2271
:param transport: The transport the .knit is located on.
2272
:param filename: The filename of the .knit.
2844
if missing_compression_parents:
2845
# This may appear to be incorrect (it does not check for
2846
# compression parents that are in the existing graph index),
2847
# but such records won't have been buffered, so this is
2848
# actually correct: every entry when
2849
# missing_compression_parents==True either has a missing parent, or
2850
# a parent that is one of the keys in records.
2851
compression_parents.difference_update(keys)
2852
self._missing_compression_parents.update(compression_parents)
2853
# Adding records may have satisfied missing compression parents.
2854
self._missing_compression_parents.difference_update(keys)
2856
def scan_unvalidated_index(self, graph_index):
2857
"""Inform this _KnitGraphIndex that there is an unvalidated index.
2859
This allows this _KnitGraphIndex to keep track of any missing
2860
compression parents we may want to have filled in to make those
2863
:param graph_index: A GraphIndex
2866
new_missing = graph_index.external_references(ref_list_num=1)
2867
new_missing.difference_update(self.get_parent_map(new_missing))
2868
self._missing_compression_parents.update(new_missing)
2869
if self._key_dependencies is not None:
2870
# Add parent refs from graph_index (and discard parent refs that
2871
# the graph_index has).
2872
for node in graph_index.iter_all_entries():
2873
self._key_dependencies.add_references(node[1], node[3][0])
2875
def get_missing_compression_parents(self):
2876
"""Return the keys of missing compression parents.
2878
Missing compression parents occur when a record stream was missing
2879
basis texts, or a index was scanned that had missing basis texts.
2881
return frozenset(self._missing_compression_parents)
2883
def get_missing_parents(self):
2884
"""Return the keys of missing parents."""
2885
# If updating this, you should also update
2886
# groupcompress._GCGraphIndex.get_missing_parents
2887
# We may have false positives, so filter those out.
2888
self._key_dependencies.add_keys(
2889
self.get_parent_map(self._key_dependencies.get_unsatisfied_refs()))
2890
return frozenset(self._key_dependencies.get_unsatisfied_refs())
2892
def _check_read(self):
2893
"""raise if reads are not permitted."""
2894
if not self._is_locked():
2895
raise errors.ObjectNotLocked(self)
2897
def _check_write_ok(self):
2898
"""Assert if writes are not permitted."""
2899
if not self._is_locked():
2900
raise errors.ObjectNotLocked(self)
2902
def _compression_parent(self, an_entry):
2903
# return the key that an_entry is compressed against, or None
2904
# Grab the second parent list (as deltas implies parents currently)
2905
compression_parents = an_entry[3][1]
2906
if not compression_parents:
2908
if len(compression_parents) != 1:
2909
raise AssertionError(
2910
"Too many compression parents: %r" % compression_parents)
2911
return compression_parents[0]
2913
def get_build_details(self, keys):
2914
"""Get the method, index_memo and compression parent for version_ids.
2916
Ghosts are omitted from the result.
2918
:param keys: An iterable of keys.
2919
:return: A dict of key:
2920
(index_memo, compression_parent, parents, record_details).
2922
opaque structure to pass to read_records to extract the raw
2925
Content that this record is built upon, may be None
2927
Logical parents of this node
2929
extra information about the content which needs to be passed to
2930
Factory.parse_record
2934
entries = self._get_entries(keys, False)
2935
for entry in entries:
2937
if not self._parents:
2940
parents = entry[3][0]
2941
if not self._deltas:
2942
compression_parent_key = None
2944
compression_parent_key = self._compression_parent(entry)
2945
noeol = (entry[2][0] == 'N')
2946
if compression_parent_key:
2947
method = 'line-delta'
2950
result[key] = (self._node_to_position(entry),
2951
compression_parent_key, parents,
2955
def _get_entries(self, keys, check_present=False):
2956
"""Get the entries for keys.
2958
:param keys: An iterable of index key tuples.
2963
for node in self._graph_index.iter_entries(keys):
2965
found_keys.add(node[1])
2967
# adapt parentless index to the rest of the code.
2968
for node in self._graph_index.iter_entries(keys):
2969
yield node[0], node[1], node[2], ()
2970
found_keys.add(node[1])
2972
missing_keys = keys.difference(found_keys)
2974
raise RevisionNotPresent(missing_keys.pop(), self)
2976
def get_method(self, key):
2977
"""Return compression method of specified key."""
2978
return self._get_method(self._get_node(key))
2980
def _get_method(self, node):
2981
if not self._deltas:
2983
if self._compression_parent(node):
2988
def _get_node(self, key):
2990
return list(self._get_entries([key]))[0]
2992
raise RevisionNotPresent(key, self)
2994
def get_options(self, key):
2995
"""Return a list representing options.
2999
node = self._get_node(key)
3000
options = [self._get_method(node)]
3001
if node[2][0] == 'N':
3002
options.append('no-eol')
3005
def get_parent_map(self, keys):
3006
"""Get a map of the parents of keys.
3008
:param keys: The keys to look up parents for.
3009
:return: A mapping from keys to parents. Absent keys are absent from
3013
nodes = self._get_entries(keys)
3017
result[node[1]] = node[3][0]
3020
result[node[1]] = None
3023
def get_position(self, key):
3024
"""Return details needed to access the version.
3026
:return: a tuple (index, data position, size) to hand to the access
3027
logic to get the record.
3029
node = self._get_node(key)
3030
return self._node_to_position(node)
3032
has_key = _mod_index._has_key_from_parent_map
3035
"""Get all the keys in the collection.
3037
The keys are not ordered.
3040
return [node[1] for node in self._graph_index.iter_all_entries()]
3042
missing_keys = _mod_index._missing_keys_from_parent_map
3044
def _node_to_position(self, node):
3045
"""Convert an index value to position details."""
3046
bits = node[2][1:].split(' ')
3047
return node[0], int(bits[0]), int(bits[1])
3049
def _sort_keys_by_io(self, keys, positions):
3050
"""Figure out an optimal order to read the records for the given keys.
3052
Sort keys, grouped by index and sorted by position.
3054
:param keys: A list of keys whose records we want to read. This will be
3056
:param positions: A dict, such as the one returned by
3057
_get_components_positions()
3060
def get_index_memo(key):
3061
# index_memo is at offset [1]. It is made up of (GraphIndex,
3062
# position, size). GI is an object, which will be unique for each
3063
# pack file. This causes us to group by pack file, then sort by
3064
# position. Size doesn't matter, but it isn't worth breaking up the
3066
return positions[key][1]
3067
return keys.sort(key=get_index_memo)
3069
_get_total_build_size = _get_total_build_size
3072
class _KnitKeyAccess(object):
3073
"""Access to records in .knit files."""
3075
def __init__(self, transport, mapper):
3076
"""Create a _KnitKeyAccess with transport and mapper.
3078
:param transport: The transport the access object is rooted at.
3079
:param mapper: The mapper used to map keys to .knit files.
2274
3081
self._transport = transport
2275
self._filename = filename
2276
self._file_mode = _file_mode
2277
self._dir_mode = _dir_mode
2278
self._need_to_create = _need_to_create
2279
self._create_parent_dir = _create_parent_dir
3082
self._mapper = mapper
2281
def add_raw_records(self, sizes, raw_data):
3084
def add_raw_records(self, key_sizes, raw_data):
2282
3085
"""Add raw knit bytes to a storage area.
2284
The data is spooled to whereever the access method is storing data.
3087
The data is spooled to the container writer in one bytes-record per
2286
:param sizes: An iterable containing the size of each raw data segment.
3090
:param sizes: An iterable of tuples containing the key and size of each
2287
3092
:param raw_data: A bytestring containing the data.
2288
:return: A list of memos to retrieve the record later. Each memo is a
2289
tuple - (index, pos, length), where the index field is always None
2290
for the .knit access method.
3093
:return: A list of memos to retrieve the record later. Each memo is an
3094
opaque index memo. For _KnitKeyAccess the memo is (key, pos,
3095
length), where the key is the record key.
2292
if not self._need_to_create:
2293
base = self._transport.append_bytes(self._filename, raw_data)
2295
self._transport.put_bytes_non_atomic(self._filename, raw_data,
2296
create_parent_dir=self._create_parent_dir,
2297
mode=self._file_mode,
2298
dir_mode=self._dir_mode)
2299
self._need_to_create = False
3097
if type(raw_data) != str:
3098
raise AssertionError(
3099
'data must be plain bytes was %s' % type(raw_data))
2303
result.append((None, base, size))
3102
# TODO: This can be tuned for writing to sftp and other servers where
3103
# append() is relatively expensive by grouping the writes to each key
3105
for key, size in key_sizes:
3106
path = self._mapper.map(key)
3108
base = self._transport.append_bytes(path + '.knit',
3109
raw_data[offset:offset+size])
3110
except errors.NoSuchFile:
3111
self._transport.mkdir(osutils.dirname(path))
3112
base = self._transport.append_bytes(path + '.knit',
3113
raw_data[offset:offset+size])
3117
result.append((key, base, size))
2308
"""IFF this data access has its own storage area, initialise it.
2312
self._transport.put_bytes_non_atomic(self._filename, '',
2313
mode=self._file_mode)
2315
def open_file(self):
2316
"""IFF this data access can be represented as a single file, open it.
2318
For knits that are not mapped to a single file on disk this will
2321
:return: None or a file handle.
2324
return self._transport.get(self._filename)
3121
"""Flush pending writes on this access object.
3123
For .knit files this is a no-op.
2329
3127
def get_raw_records(self, memos_for_retrieval):
2330
3128
"""Get the raw bytes for a records.
2332
:param memos_for_retrieval: An iterable containing the (index, pos,
2333
length) memo for retrieving the bytes. The .knit method ignores
2334
the index as there is always only a single file.
3130
:param memos_for_retrieval: An iterable containing the access memo for
3131
retrieving the bytes.
2335
3132
:return: An iterator over the bytes of the records.
2337
read_vector = [(pos, size) for (index, pos, size) in memos_for_retrieval]
2338
for pos, data in self._transport.readv(self._filename, read_vector):
2342
class _PackAccess(object):
2343
"""Access to knit records via a collection of packs."""
2345
def __init__(self, index_to_packs, writer=None):
2346
"""Create a _PackAccess object.
3134
# first pass, group into same-index request to minimise readv's issued.
3136
current_prefix = None
3137
for (key, offset, length) in memos_for_retrieval:
3138
if current_prefix == key[:-1]:
3139
current_list.append((offset, length))
3141
if current_prefix is not None:
3142
request_lists.append((current_prefix, current_list))
3143
current_prefix = key[:-1]
3144
current_list = [(offset, length)]
3145
# handle the last entry
3146
if current_prefix is not None:
3147
request_lists.append((current_prefix, current_list))
3148
for prefix, read_vector in request_lists:
3149
path = self._mapper.map(prefix) + '.knit'
3150
for pos, data in self._transport.readv(path, read_vector):
3154
class _DirectPackAccess(object):
3155
"""Access to data in one or more packs with less translation."""
3157
def __init__(self, index_to_packs, reload_func=None, flush_func=None):
3158
"""Create a _DirectPackAccess object.
2348
3160
:param index_to_packs: A dict mapping index objects to the transport
2349
3161
and file names for obtaining data.
2350
:param writer: A tuple (pack.ContainerWriter, write_index) which
2351
contains the pack to write, and the index that reads from it will
3162
:param reload_func: A function to call if we determine that the pack
3163
files have moved and we need to reload our caches. See
3164
bzrlib.repo_fmt.pack_repo.AggregateIndex for more details.
2355
self.container_writer = writer[0]
2356
self.write_index = writer[1]
2358
self.container_writer = None
2359
self.write_index = None
2360
self.indices = index_to_packs
3166
self._container_writer = None
3167
self._write_index = None
3168
self._indices = index_to_packs
3169
self._reload_func = reload_func
3170
self._flush_func = flush_func
2362
def add_raw_records(self, sizes, raw_data):
3172
def add_raw_records(self, key_sizes, raw_data):
2363
3173
"""Add raw knit bytes to a storage area.
2365
3175
The data is spooled to the container writer in one bytes-record per
2368
:param sizes: An iterable containing the size of each raw data segment.
3178
:param sizes: An iterable of tuples containing the key and size of each
2369
3180
:param raw_data: A bytestring containing the data.
2370
:return: A list of memos to retrieve the record later. Each memo is a
2371
tuple - (index, pos, length), where the index field is the
2372
write_index object supplied to the PackAccess object.
3181
:return: A list of memos to retrieve the record later. Each memo is an
3182
opaque index memo. For _DirectPackAccess the memo is (index, pos,
3183
length), where the index field is the write_index object supplied
3184
to the PackAccess object.
3186
if type(raw_data) != str:
3187
raise AssertionError(
3188
'data must be plain bytes was %s' % type(raw_data))
2377
p_offset, p_length = self.container_writer.add_bytes_record(
3191
for key, size in key_sizes:
3192
p_offset, p_length = self._container_writer.add_bytes_record(
2378
3193
raw_data[offset:offset+size], [])
2380
result.append((self.write_index, p_offset, p_length))
3195
result.append((self._write_index, p_offset, p_length))
2384
"""Pack based knits do not get individually created."""
3199
"""Flush pending writes on this access object.
3201
This will flush any buffered writes to a NewPack.
3203
if self._flush_func is not None:
2386
3206
def get_raw_records(self, memos_for_retrieval):
2387
3207
"""Get the raw bytes for a records.
2389
:param memos_for_retrieval: An iterable containing the (index, pos,
3209
:param memos_for_retrieval: An iterable containing the (index, pos,
2390
3210
length) memo for retrieving the bytes. The Pack access method
2391
3211
looks up the pack to use for a given record in its index_to_pack
2407
3227
if current_index is not None:
2408
3228
request_lists.append((current_index, current_list))
2409
3229
for index, offsets in request_lists:
2410
transport, path = self.indices[index]
2411
reader = pack.make_readv_reader(transport, path, offsets)
2412
for names, read_func in reader.iter_records():
2413
yield read_func(None)
2415
def open_file(self):
2416
"""Pack based knits have no single file."""
2419
def set_writer(self, writer, index, (transport, packname)):
3231
transport, path = self._indices[index]
3233
# A KeyError here indicates that someone has triggered an index
3234
# reload, and this index has gone missing, we need to start
3236
if self._reload_func is None:
3237
# If we don't have a _reload_func there is nothing that can
3240
raise errors.RetryWithNewPacks(index,
3241
reload_occurred=True,
3242
exc_info=sys.exc_info())
3244
reader = pack.make_readv_reader(transport, path, offsets)
3245
for names, read_func in reader.iter_records():
3246
yield read_func(None)
3247
except errors.NoSuchFile:
3248
# A NoSuchFile error indicates that a pack file has gone
3249
# missing on disk, we need to trigger a reload, and start over.
3250
if self._reload_func is None:
3252
raise errors.RetryWithNewPacks(transport.abspath(path),
3253
reload_occurred=False,
3254
exc_info=sys.exc_info())
3256
def set_writer(self, writer, index, transport_packname):
2420
3257
"""Set a writer to use for adding data."""
2421
3258
if index is not None:
2422
self.indices[index] = (transport, packname)
2423
self.container_writer = writer
2424
self.write_index = index
2427
class _StreamAccess(object):
2428
"""A Knit Access object that provides data from a datastream.
2430
It also provides a fallback to present as unannotated data, annotated data
2431
from a *backing* access object.
2433
This is triggered by a index_memo which is pointing to a different index
2434
than this was constructed with, and is used to allow extracting full
2435
unannotated texts for insertion into annotated knits.
2438
def __init__(self, reader_callable, stream_index, backing_knit,
2440
"""Create a _StreamAccess object.
2442
:param reader_callable: The reader_callable from the datastream.
2443
This is called to buffer all the data immediately, for
2445
:param stream_index: The index the data stream this provides access to
2446
which will be present in native index_memo's.
2447
:param backing_knit: The knit object that will provide access to
2448
annotated texts which are not available in the stream, so as to
2449
create unannotated texts.
2450
:param orig_factory: The original content factory used to generate the
2451
stream. This is used for checking whether the thunk code for
2452
supporting _copy_texts will generate the correct form of data.
2454
self.data = reader_callable(None)
2455
self.stream_index = stream_index
2456
self.backing_knit = backing_knit
2457
self.orig_factory = orig_factory
2459
def get_raw_records(self, memos_for_retrieval):
2460
"""Get the raw bytes for a records.
2462
:param memos_for_retrieval: An iterable of memos from the
2463
_StreamIndex object identifying bytes to read; for these classes
2464
they are (from_backing_knit, index, start, end) and can point to
2465
either the backing knit or streamed data.
2466
:return: An iterator yielding a byte string for each record in
2467
memos_for_retrieval.
2469
# use a generator for memory friendliness
2470
for from_backing_knit, version_id, start, end in memos_for_retrieval:
2471
if not from_backing_knit:
2472
if version_id is not self.stream_index:
2473
raise AssertionError()
2474
yield self.data[start:end]
2476
# we have been asked to thunk. This thunking only occurs when
2477
# we are obtaining plain texts from an annotated backing knit
2478
# so that _copy_texts will work.
2479
# We could improve performance here by scanning for where we need
2480
# to do this and using get_line_list, then interleaving the output
2481
# as desired. However, for now, this is sufficient.
2482
if self.orig_factory.__class__ != KnitPlainFactory:
2483
raise errors.KnitCorrupt(
2484
self, 'Bad thunk request %r cannot be backed by %r' %
2485
(version_id, self.orig_factory))
2486
lines = self.backing_knit.get_lines(version_id)
2487
line_bytes = ''.join(lines)
2488
digest = sha_string(line_bytes)
2489
# the packed form of the fulltext always has a trailing newline,
2490
# even if the actual text does not, unless the file is empty. the
2491
# record options including the noeol flag are passed through by
2492
# _StreamIndex, so this is safe.
2494
if lines[-1][-1] != '\n':
2495
lines[-1] = lines[-1] + '\n'
2497
# We want plain data, because we expect to thunk only to allow text
2499
size, bytes = self.backing_knit._data._record_to_data(version_id,
2500
digest, lines, line_bytes)
2504
class _StreamIndex(object):
2505
"""A Knit Index object that uses the data map from a datastream."""
2507
def __init__(self, data_list, backing_index):
2508
"""Create a _StreamIndex object.
2510
:param data_list: The data_list from the datastream.
2511
:param backing_index: The index which will supply values for nodes
2512
referenced outside of this stream.
2514
self.data_list = data_list
2515
self.backing_index = backing_index
2516
self._by_version = {}
2518
for key, options, length, parents in data_list:
2519
self._by_version[key] = options, (pos, pos + length), parents
2522
def get_ancestry(self, versions, topo_sorted):
2523
"""Get an ancestry list for versions."""
2525
# Not needed for basic joins
2526
raise NotImplementedError(self.get_ancestry)
2527
# get a graph of all the mentioned versions:
2528
# Little ugly - basically copied from KnitIndex, but don't want to
2529
# accidentally incorporate too much of that index's code.
2531
pending = set(versions)
2532
cache = self._by_version
2534
version = pending.pop()
2537
parents = [p for p in cache[version][2] if p in cache]
2539
raise RevisionNotPresent(version, self)
2540
# if not completed and not a ghost
2541
pending.update([p for p in parents if p not in ancestry])
2542
ancestry.add(version)
2543
return list(ancestry)
2545
def get_build_details(self, version_ids):
2546
"""Get the method, index_memo and compression parent for version_ids.
2548
Ghosts are omitted from the result.
2550
:param version_ids: An iterable of version_ids.
2551
:return: A dict of version_id:(index_memo, compression_parent,
2552
parents, record_details).
2554
opaque memo that can be passed to _StreamAccess.read_records
2555
to extract the raw data; for these classes it is
2556
(from_backing_knit, index, start, end)
2558
Content that this record is built upon, may be None
2560
Logical parents of this node
2562
extra information about the content which needs to be passed to
2563
Factory.parse_record
2566
for version_id in version_ids:
2568
method = self.get_method(version_id)
2569
except errors.RevisionNotPresent:
2570
# ghosts are omitted
2572
parent_ids = self.get_parents_with_ghosts(version_id)
2573
noeol = ('no-eol' in self.get_options(version_id))
2574
index_memo = self.get_position(version_id)
2575
from_backing_knit = index_memo[0]
2576
if from_backing_knit:
2577
# texts retrieved from the backing knit are always full texts
2579
if method == 'fulltext':
2580
compression_parent = None
2582
compression_parent = parent_ids[0]
2583
result[version_id] = (index_memo, compression_parent,
2584
parent_ids, (method, noeol))
2587
def get_method(self, version_id):
2588
"""Return compression method of specified version."""
2589
options = self.get_options(version_id)
2590
if 'fulltext' in options:
2592
elif 'line-delta' in options:
2595
raise errors.KnitIndexUnknownMethod(self, options)
2597
def get_options(self, version_id):
2598
"""Return a list representing options.
2603
return self._by_version[version_id][0]
2605
options = list(self.backing_index.get_options(version_id))
2606
if 'fulltext' in options:
2608
elif 'line-delta' in options:
2609
# Texts from the backing knit are always returned from the stream
2611
options.remove('line-delta')
2612
options.append('fulltext')
2614
raise errors.KnitIndexUnknownMethod(self, options)
2615
return tuple(options)
2617
def get_parent_map(self, version_ids):
2618
"""Passed through to by KnitVersionedFile.get_parent_map."""
2621
for version_id in version_ids:
2623
result[version_id] = self._by_version[version_id][2]
2625
pending_ids.add(version_id)
2626
result.update(self.backing_index.get_parent_map(pending_ids))
2629
def get_parents_with_ghosts(self, version_id):
2630
"""Return parents of specified version with ghosts."""
2632
return self.get_parent_map([version_id])[version_id]
2634
raise RevisionNotPresent(version_id, self)
2636
def get_position(self, version_id):
2637
"""Return details needed to access the version.
2639
_StreamAccess has the data as a big array, so we return slice
2640
coordinates into that (as index_memo's are opaque outside the
2641
index and matching access class).
2643
:return: a tuple (from_backing_knit, index, start, end) that can
2644
be passed e.g. to get_raw_records.
2645
If from_backing_knit is False, index will be self, otherwise it
2646
will be a version id.
2649
start, end = self._by_version[version_id][1]
2650
return False, self, start, end
2652
# Signal to the access object to handle this from the backing knit.
2653
return (True, version_id, None, None)
2655
def get_versions(self):
2656
"""Get all the versions in the stream."""
2657
return self._by_version.keys()
2660
class _KnitData(object):
2661
"""Manage extraction of data from a KnitAccess, caching and decompressing.
2663
The KnitData class provides the logic for parsing and using knit records,
2664
making use of an access method for the low level read and write operations.
2667
def __init__(self, access):
2668
"""Create a KnitData object.
2670
:param access: The access method to use. Access methods such as
2671
_KnitAccess manage the insertion of raw records and the subsequent
2672
retrieval of the same.
2674
self._access = access
2675
self._checked = False
2677
def _open_file(self):
2678
return self._access.open_file()
2680
def _record_to_data(self, version_id, digest, lines, dense_lines=None):
2681
"""Convert version_id, digest, lines into a raw data block.
2683
:param dense_lines: The bytes of lines but in a denser form. For
2684
instance, if lines is a list of 1000 bytestrings each ending in \n,
2685
dense_lines may be a list with one line in it, containing all the
2686
1000's lines and their \n's. Using dense_lines if it is already
2687
known is a win because the string join to create bytes in this
2688
function spends less time resizing the final string.
2689
:return: (len, a StringIO instance with the raw data ready to read.)
2691
# Note: using a string copy here increases memory pressure with e.g.
2692
# ISO's, but it is about 3 seconds faster on a 1.2Ghz intel machine
2693
# when doing the initial commit of a mozilla tree. RBC 20070921
2694
bytes = ''.join(chain(
2695
["version %s %d %s\n" % (version_id,
2698
dense_lines or lines,
2699
["end %s\n" % version_id]))
2700
compressed_bytes = bytes_to_gzip(bytes)
2701
return len(compressed_bytes), compressed_bytes
2703
def add_raw_records(self, sizes, raw_data):
2704
"""Append a prepared record to the data file.
2706
:param sizes: An iterable containing the size of each raw data segment.
2707
:param raw_data: A bytestring containing the data.
2708
:return: a list of index data for the way the data was stored.
2709
See the access method add_raw_records documentation for more
2712
return self._access.add_raw_records(sizes, raw_data)
2714
def _parse_record_header(self, version_id, raw_data):
2715
"""Parse a record header for consistency.
2717
:return: the header and the decompressor stream.
2718
as (stream, header_record)
2720
df = GzipFile(mode='rb', fileobj=StringIO(raw_data))
2722
rec = self._check_header(version_id, df.readline())
2723
except Exception, e:
2724
raise KnitCorrupt(self._access,
2725
"While reading {%s} got %s(%s)"
2726
% (version_id, e.__class__.__name__, str(e)))
2729
def _split_header(self, line):
2732
raise KnitCorrupt(self._access,
2733
'unexpected number of elements in record header')
2736
def _check_header_version(self, rec, version_id):
2737
if rec[1] != version_id:
2738
raise KnitCorrupt(self._access,
2739
'unexpected version, wanted %r, got %r'
2740
% (version_id, rec[1]))
2742
def _check_header(self, version_id, line):
2743
rec = self._split_header(line)
2744
self._check_header_version(rec, version_id)
2747
def _parse_record_unchecked(self, data):
2749
# 4168 calls in 2880 217 internal
2750
# 4168 calls to _parse_record_header in 2121
2751
# 4168 calls to readlines in 330
2752
df = GzipFile(mode='rb', fileobj=StringIO(data))
2754
record_contents = df.readlines()
2755
except Exception, e:
2756
raise KnitCorrupt(self._access, "Corrupt compressed record %r, got %s(%s)" %
2757
(data, e.__class__.__name__, str(e)))
2758
header = record_contents.pop(0)
2759
rec = self._split_header(header)
2760
last_line = record_contents.pop()
2761
if len(record_contents) != int(rec[2]):
2762
raise KnitCorrupt(self._access,
2763
'incorrect number of lines %s != %s'
2765
% (len(record_contents), int(rec[2]),
2767
if last_line != 'end %s\n' % rec[1]:
2768
raise KnitCorrupt(self._access,
2769
'unexpected version end line %r, wanted %r'
2770
% (last_line, rec[1]))
2772
return rec, record_contents
2774
def _parse_record(self, version_id, data):
2775
rec, record_contents = self._parse_record_unchecked(data)
2776
self._check_header_version(rec, version_id)
2777
return record_contents, rec[3]
2779
def read_records_iter_raw(self, records):
2780
"""Read text records from data file and yield raw data.
2782
This unpacks enough of the text record to validate the id is
2783
as expected but thats all.
2785
Each item the iterator yields is (version_id, bytes,
2788
# setup an iterator of the external records:
2789
# uses readv so nice and fast we hope.
2791
# grab the disk data needed.
2792
needed_offsets = [index_memo for version_id, index_memo
2794
raw_records = self._access.get_raw_records(needed_offsets)
2796
for version_id, index_memo in records:
2797
data = raw_records.next()
2798
# validate the header
2799
df, rec = self._parse_record_header(version_id, data)
2801
yield version_id, data, rec[3]
2803
def read_records_iter(self, records):
2804
"""Read text records from data file and yield result.
2806
The result will be returned in whatever is the fastest to read.
2807
Not by the order requested. Also, multiple requests for the same
2808
record will only yield 1 response.
2809
:param records: A list of (version_id, pos, len) entries
2810
:return: Yields (version_id, contents, digest) in the order
2811
read, not the order requested
2816
needed_records = sorted(set(records), key=operator.itemgetter(1))
2817
if not needed_records:
2820
# The transport optimizes the fetching as well
2821
# (ie, reads continuous ranges.)
2822
raw_data = self._access.get_raw_records(
2823
[index_memo for version_id, index_memo in needed_records])
2825
for (version_id, index_memo), data in \
2826
izip(iter(needed_records), raw_data):
2827
content, digest = self._parse_record(version_id, data)
2828
yield version_id, content, digest
2830
def read_records(self, records):
2831
"""Read records into a dictionary."""
2833
for record_id, content, digest in \
2834
self.read_records_iter(records):
2835
components[record_id] = (content, digest)
2839
class InterKnit(InterVersionedFile):
2840
"""Optimised code paths for knit to knit operations."""
2842
_matching_file_from_factory = staticmethod(make_file_knit)
2843
_matching_file_to_factory = staticmethod(make_file_knit)
2846
def is_compatible(source, target):
2847
"""Be compatible with knits. """
2849
return (isinstance(source, KnitVersionedFile) and
2850
isinstance(target, KnitVersionedFile))
2851
except AttributeError:
2854
def _copy_texts(self, pb, msg, version_ids, ignore_missing=False):
2855
"""Copy texts to the target by extracting and adding them one by one.
2857
see join() for the parameter definitions.
2859
version_ids = self._get_source_version_ids(version_ids, ignore_missing)
2860
# --- the below is factorable out with VersionedFile.join, but wait for
2861
# VersionedFiles, it may all be simpler then.
2862
graph = Graph(self.source)
2863
search = graph._make_breadth_first_searcher(version_ids)
2864
transitive_ids = set()
2865
map(transitive_ids.update, list(search))
2866
parent_map = self.source.get_parent_map(transitive_ids)
2867
order = topo_sort(parent_map.items())
2869
def size_of_content(content):
2870
return sum(len(line) for line in content.text())
2871
# Cache at most 10MB of parent texts
2872
parent_cache = lru_cache.LRUSizeCache(max_size=10*1024*1024,
2873
compute_size=size_of_content)
2874
# TODO: jam 20071116 It would be nice to have a streaming interface to
2875
# get multiple texts from a source. The source could be smarter
2876
# about how it handled intermediate stages.
2877
# get_line_list() or make_mpdiffs() seem like a possibility, but
2878
# at the moment they extract all full texts into memory, which
2879
# causes us to store more than our 3x fulltext goal.
2880
# Repository.iter_files_bytes() may be another possibility
2881
to_process = [version for version in order
2882
if version not in self.target]
2883
total = len(to_process)
2884
pb = ui.ui_factory.nested_progress_bar()
2886
for index, version in enumerate(to_process):
2887
pb.update('Converting versioned data', index, total)
2888
sha1, num_bytes, parent_text = self.target.add_lines(version,
2889
self.source.get_parents_with_ghosts(version),
2890
self.source.get_lines(version),
2891
parent_texts=parent_cache)
2892
parent_cache[version] = parent_text
2897
def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
2898
"""See InterVersionedFile.join."""
2899
# If the source and target are mismatched w.r.t. annotations vs
2900
# plain, the data needs to be converted accordingly
2901
if self.source.factory.annotated == self.target.factory.annotated:
2903
elif self.source.factory.annotated:
2904
converter = self._anno_to_plain_converter
2906
# We're converting from a plain to an annotated knit. Copy them
2907
# across by full texts.
2908
return self._copy_texts(pb, msg, version_ids, ignore_missing)
2910
version_ids = self._get_source_version_ids(version_ids, ignore_missing)
2914
pb = ui.ui_factory.nested_progress_bar()
2916
version_ids = list(version_ids)
2917
if None in version_ids:
2918
version_ids.remove(None)
2920
self.source_ancestry = set(self.source.get_ancestry(version_ids,
2922
this_versions = set(self.target._index.get_versions())
2923
# XXX: For efficiency we should not look at the whole index,
2924
# we only need to consider the referenced revisions - they
2925
# must all be present, or the method must be full-text.
2926
# TODO, RBC 20070919
2927
needed_versions = self.source_ancestry - this_versions
2929
if not needed_versions:
2931
full_list = topo_sort(
2932
self.source.get_parent_map(self.source.versions()))
2934
version_list = [i for i in full_list if (not self.target.has_version(i)
2935
and i in needed_versions)]
2939
copy_queue_records = []
2941
for version_id in version_list:
2942
options = self.source._index.get_options(version_id)
2943
parents = self.source._index.get_parents_with_ghosts(version_id)
2944
# check that its will be a consistent copy:
2945
for parent in parents:
2946
# if source has the parent, we must :
2947
# * already have it or
2948
# * have it scheduled already
2949
# otherwise we don't care
2950
if not (self.target.has_version(parent) or
2951
parent in copy_set or
2952
not self.source.has_version(parent)):
2953
raise AssertionError("problem joining parent %r "
2955
% (parent, self.source, self.target))
2956
index_memo = self.source._index.get_position(version_id)
2957
copy_queue_records.append((version_id, index_memo))
2958
copy_queue.append((version_id, options, parents))
2959
copy_set.add(version_id)
2961
# data suck the join:
2963
total = len(version_list)
2966
for (version_id, raw_data, _), \
2967
(version_id2, options, parents) in \
2968
izip(self.source._data.read_records_iter_raw(copy_queue_records),
2970
if not (version_id == version_id2):
2971
raise AssertionError('logic error, inconsistent results')
2973
pb.update("Joining knit", count, total)
2975
size, raw_data = converter(raw_data, version_id, options,
2978
size = len(raw_data)
2979
raw_records.append((version_id, options, parents, size))
2980
raw_datum.append(raw_data)
2981
self.target._add_raw_records(raw_records, ''.join(raw_datum))
2986
def _anno_to_plain_converter(self, raw_data, version_id, options,
2988
"""Convert annotated content to plain content."""
2989
data, digest = self.source._data._parse_record(version_id, raw_data)
2990
if 'fulltext' in options:
2991
content = self.source.factory.parse_fulltext(data, version_id)
2992
lines = self.target.factory.lower_fulltext(content)
2994
delta = self.source.factory.parse_line_delta(data, version_id,
2996
lines = self.target.factory.lower_line_delta(delta)
2997
return self.target._data._record_to_data(version_id, digest, lines)
3000
InterVersionedFile.register_optimiser(InterKnit)
3003
class WeaveToKnit(InterVersionedFile):
3004
"""Optimised code paths for weave to knit operations."""
3006
_matching_file_from_factory = bzrlib.weave.WeaveFile
3007
_matching_file_to_factory = staticmethod(make_file_knit)
3010
def is_compatible(source, target):
3011
"""Be compatible with weaves to knits."""
3013
return (isinstance(source, bzrlib.weave.Weave) and
3014
isinstance(target, KnitVersionedFile))
3015
except AttributeError:
3018
def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
3019
"""See InterVersionedFile.join."""
3020
version_ids = self._get_source_version_ids(version_ids, ignore_missing)
3025
pb = ui.ui_factory.nested_progress_bar()
3027
version_ids = list(version_ids)
3029
self.source_ancestry = set(self.source.get_ancestry(version_ids))
3030
this_versions = set(self.target._index.get_versions())
3031
needed_versions = self.source_ancestry - this_versions
3033
if not needed_versions:
3035
full_list = topo_sort(
3036
self.source.get_parent_map(self.source.versions()))
3038
version_list = [i for i in full_list if (not self.target.has_version(i)
3039
and i in needed_versions)]
3043
total = len(version_list)
3044
parent_map = self.source.get_parent_map(version_list)
3045
for version_id in version_list:
3046
pb.update("Converting to knit", count, total)
3047
parents = parent_map[version_id]
3048
# check that its will be a consistent copy:
3049
for parent in parents:
3050
# if source has the parent, we must already have it
3051
if not self.target.has_version(parent):
3052
raise AssertionError("%r does not have parent %r"
3053
% (self.target, parent))
3054
self.target.add_lines(
3055
version_id, parents, self.source.get_lines(version_id))
3062
InterVersionedFile.register_optimiser(WeaveToKnit)
3259
self._indices[index] = transport_packname
3260
self._container_writer = writer
3261
self._write_index = index
3263
def reload_or_raise(self, retry_exc):
3264
"""Try calling the reload function, or re-raise the original exception.
3266
This should be called after _DirectPackAccess raises a
3267
RetryWithNewPacks exception. This function will handle the common logic
3268
of determining when the error is fatal versus being temporary.
3269
It will also make sure that the original exception is raised, rather
3270
than the RetryWithNewPacks exception.
3272
If this function returns, then the calling function should retry
3273
whatever operation was being performed. Otherwise an exception will
3276
:param retry_exc: A RetryWithNewPacks exception.
3279
if self._reload_func is None:
3281
elif not self._reload_func():
3282
# The reload claimed that nothing changed
3283
if not retry_exc.reload_occurred:
3284
# If there wasn't an earlier reload, then we really were
3285
# expecting to find changes. We didn't find them, so this is a
3289
exc_class, exc_value, exc_traceback = retry_exc.exc_info
3290
raise exc_class, exc_value, exc_traceback
3065
3293
# Deprecated, use PatienceSequenceMatcher instead