172
172
"""The text index is the name + .tix."""
173
173
return self.index_name('text', name)
175
def _external_compression_parents_of_texts(self):
178
for node in self.text_index.iter_all_entries():
180
refs.update(node[3][1])
184
176
class ExistingPack(Pack):
185
177
"""An in memory proxy for an existing .pack and its disk indices."""
222
214
'signature': ('.six', 3),
225
def __init__(self, upload_transport, index_transport, pack_transport,
226
upload_suffix='', file_mode=None, index_builder_class=None,
217
def __init__(self, pack_collection, upload_suffix='', file_mode=None):
228
218
"""Create a NewPack instance.
230
:param upload_transport: A writable transport for the pack to be
231
incrementally uploaded to.
232
:param index_transport: A writable transport for the pack's indices to
233
be written to when the pack is finished.
234
:param pack_transport: A writable transport for the pack to be renamed
235
to when the upload is complete. This *must* be the same as
236
upload_transport.clone('../packs').
220
:param pack_collection: A PackCollection into which this is being inserted.
237
221
:param upload_suffix: An optional suffix to be given to any temporary
238
222
files created during the pack creation. e.g '.autopack'
239
:param file_mode: An optional file mode to create the new files with.
240
:param index_builder_class: Required keyword parameter - the class of
241
index builder to use.
242
:param index_class: Required keyword parameter - the class of index
223
:param file_mode: Unix permissions for newly created file.
245
225
# The relative locations of the packs are constrained, but all are
246
226
# passed in because the caller has them, so as to avoid object churn.
227
index_builder_class = pack_collection._index_builder_class
247
228
Pack.__init__(self,
248
229
# Revisions: parents list, no text compression.
249
230
index_builder_class(reference_lists=1),
260
241
index_builder_class(reference_lists=0),
243
self._pack_collection = pack_collection
262
244
# When we make readonly indices, we need this.
263
self.index_class = index_class
245
self.index_class = pack_collection._index_class
264
246
# where should the new pack be opened
265
self.upload_transport = upload_transport
247
self.upload_transport = pack_collection._upload_transport
266
248
# where are indices written out to
267
self.index_transport = index_transport
249
self.index_transport = pack_collection._index_transport
268
250
# where is the pack renamed to when it is finished?
269
self.pack_transport = pack_transport
251
self.pack_transport = pack_collection._pack_transport
270
252
# What file mode to upload the pack and indices with.
271
253
self._file_mode = file_mode
272
254
# tracks the content written to the .pack file.
335
317
raise AssertionError(self._state)
319
def _check_references(self):
320
"""Make sure our external references are present.
322
Packs are allowed to have deltas whose base is not in the pack, but it
323
must be present somewhere in this collection. It is not allowed to
324
have deltas based on a fallback repository.
325
(See <https://bugs.launchpad.net/bzr/+bug/288751>)
328
for (index_name, external_refs, index) in [
330
self.text_index._external_references(),
331
self._pack_collection.text_index.combined_index),
333
self.inventory_index._external_references(),
334
self._pack_collection.inventory_index.combined_index),
336
missing = external_refs.difference(
337
k for (idx, k, v, r) in
338
index.iter_entries(external_refs))
340
missing_items[index_name] = sorted(list(missing))
342
from pprint import pformat
343
raise errors.BzrCheckError(
344
"Newly created pack file %r has delta references to "
345
"items not in its repository:\n%s"
346
% (self, pformat(missing_items)))
337
348
def data_inserted(self):
338
349
"""True if data has been added to this pack."""
339
350
return bool(self.get_revision_count() or
356
367
if self._buffer[1]:
357
368
self._write_data('', flush=True)
358
369
self.name = self._hash.hexdigest()
370
self._check_references()
360
372
# XXX: It'd be better to write them all to temporary names, then
361
373
# rename them all into place, so that the window when only some are
462
474
self._reload_func = reload_func
463
475
self.index_to_pack = {}
464
476
self.combined_index = CombinedGraphIndex([], reload_func=reload_func)
465
self.data_access = _DirectPackAccess(self.index_to_pack)
477
self.data_access = _DirectPackAccess(self.index_to_pack,
478
reload_func=reload_func)
466
479
self.add_callback = None
468
481
def replace_indices(self, index_to_pack, indices):
568
581
def _extra_init(self):
569
582
"""A template hook to allow extending the constructor trivially."""
584
def _pack_map_and_index_list(self, index_attribute):
585
"""Convert a list of packs to an index pack map and index list.
587
:param index_attribute: The attribute that the desired index is found
589
:return: A tuple (map, list) where map contains the dict from
590
index:pack_tuple, and list contains the indices in the preferred
595
for pack_obj in self.packs:
596
index = getattr(pack_obj, index_attribute)
597
indices.append(index)
598
pack_map[index] = pack_obj
599
return pack_map, indices
601
def _index_contents(self, indices, key_filter=None):
602
"""Get an iterable of the index contents from a pack_map.
604
:param indices: The list of indices to query
605
:param key_filter: An optional filter to limit the keys returned.
607
all_index = CombinedGraphIndex(indices)
608
if key_filter is None:
609
return all_index.iter_all_entries()
611
return all_index.iter_entries(key_filter)
571
613
def pack(self, pb=None):
572
614
"""Create a new pack by reading data from other packs.
610
652
def open_pack(self):
611
653
"""Open a pack for the pack we are creating."""
612
return NewPack(self._pack_collection._upload_transport,
613
self._pack_collection._index_transport,
614
self._pack_collection._pack_transport, upload_suffix=self.suffix,
615
file_mode=self._pack_collection.repo.bzrdir._get_file_mode(),
616
index_builder_class=self._pack_collection._index_builder_class,
617
index_class=self._pack_collection._index_class)
654
return NewPack(self._pack_collection, upload_suffix=self.suffix,
655
file_mode=self._pack_collection.repo.bzrdir._get_file_mode())
657
def _update_pack_order(self, entries, index_to_pack_map):
658
"""Determine how we want our packs to be ordered.
660
This changes the sort order of the self.packs list so that packs unused
661
by 'entries' will be at the end of the list, so that future requests
662
can avoid probing them. Used packs will be at the front of the
663
self.packs list, in the order of their first use in 'entries'.
665
:param entries: A list of (index, ...) tuples
666
:param index_to_pack_map: A mapping from index objects to pack objects.
670
for entry in entries:
672
if index not in seen_indexes:
673
packs.append(index_to_pack_map[index])
674
seen_indexes.add(index)
675
if len(packs) == len(self.packs):
676
if 'pack' in debug.debug_flags:
677
mutter('Not changing pack list, all packs used.')
679
seen_packs = set(packs)
680
for pack in self.packs:
681
if pack not in seen_packs:
684
if 'pack' in debug.debug_flags:
685
old_names = [p.access_tuple()[1] for p in self.packs]
686
new_names = [p.access_tuple()[1] for p in packs]
687
mutter('Reordering packs\nfrom: %s\n to: %s',
688
old_names, new_names)
619
691
def _copy_revision_texts(self):
620
692
"""Copy revision data to the new pack."""
625
697
revision_keys = None
626
698
# select revision keys
627
revision_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
628
self.packs, 'revision_index')[0]
629
revision_nodes = self._pack_collection._index_contents(revision_index_map, revision_keys)
699
revision_index_map, revision_indices = self._pack_map_and_index_list(
701
revision_nodes = self._index_contents(revision_indices, revision_keys)
702
revision_nodes = list(revision_nodes)
703
self._update_pack_order(revision_nodes, revision_index_map)
630
704
# copy revision keys and adjust values
631
705
self.pb.update("Copying revision texts", 1)
632
706
total_items, readv_group_iter = self._revision_node_readv(revision_nodes)
652
726
# querying for keys here could introduce a bug where an inventory item
653
727
# is missed, so do not change it to query separately without cross
654
728
# checking like the text key check below.
655
inventory_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
656
self.packs, 'inventory_index')[0]
657
inv_nodes = self._pack_collection._index_contents(inventory_index_map, inv_keys)
729
inventory_index_map, inventory_indices = self._pack_map_and_index_list(
731
inv_nodes = self._index_contents(inventory_indices, inv_keys)
658
732
# copy inventory keys and adjust values
659
733
# XXX: Should be a helper function to allow different inv representation
704
778
self.new_pack.text_index, readv_group_iter, total_items))
705
779
self._log_copied_texts()
707
def _check_references(self):
708
"""Make sure our external refereneces are present."""
709
external_refs = self.new_pack._external_compression_parents_of_texts()
711
index = self._pack_collection.text_index.combined_index
712
found_items = list(index.iter_entries(external_refs))
713
if len(found_items) != len(external_refs):
714
found_keys = set(k for idx, k, refs, value in found_items)
715
missing_items = external_refs - found_keys
716
missing_file_id, missing_revision_id = missing_items.pop()
717
raise errors.RevisionNotPresent(missing_revision_id,
720
781
def _create_pack_from_packs(self):
721
782
self.pb.update("Opening pack", 0, 5)
722
783
self.new_pack = self.open_pack()
740
801
self._copy_text_texts()
741
802
# select signature keys
742
803
signature_filter = self._revision_keys # same keyspace
743
signature_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
744
self.packs, 'signature_index')[0]
745
signature_nodes = self._pack_collection._index_contents(signature_index_map,
804
signature_index_map, signature_indices = self._pack_map_and_index_list(
806
signature_nodes = self._index_contents(signature_indices,
746
807
signature_filter)
747
808
# copy signature keys and adjust values
748
809
self.pb.update("Copying signature texts", 4)
753
814
time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
754
815
new_pack.signature_index.key_count(),
755
816
time.time() - new_pack.start_time)
756
self._check_references()
817
new_pack._check_references()
757
818
if not self._use_pack(new_pack):
798
859
# linear scan up the pack
799
860
pack_readv_requests.sort()
801
transport, path = index_map[index]
862
pack_obj = index_map[index]
863
transport, path = pack_obj.access_tuple()
802
864
reader = pack.make_readv_reader(transport, path,
803
865
[offset[0:2] for offset in pack_readv_requests])
804
866
for (names, read_func), (_1, _2, (key, eol_flag)) in \
842
904
pb.update("Copied record", record_index, total_items)
843
905
for index, readv_vector, node_vector in readv_group_iter:
845
transport, path = index_map[index]
907
pack_obj = index_map[index]
908
transport, path = pack_obj.access_tuple()
846
909
reader = pack.make_readv_reader(transport, path, readv_vector)
847
910
for (names, read_func), (key, eol_flag, references) in \
848
911
izip(reader.iter_records(), node_vector):
866
929
record_index += 1
868
931
def _get_text_nodes(self):
869
text_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
870
self.packs, 'text_index')[0]
871
return text_index_map, self._pack_collection._index_contents(text_index_map,
932
text_index_map, text_indices = self._pack_map_and_index_list(
934
return text_index_map, self._index_contents(text_indices,
872
935
self._text_filter)
874
937
def _least_readv_node_readv(self, nodes):
1109
1172
output_texts.add_lines(key, parent_keys, text_lines,
1110
1173
random_id=True, check_content=False)
1111
1174
# 5) check that nothing inserted has a reference outside the keyspace.
1112
missing_text_keys = self.new_pack._external_compression_parents_of_texts()
1175
missing_text_keys = self.new_pack.text_index._external_references()
1113
1176
if missing_text_keys:
1114
raise errors.BzrError('Reference to missing compression parents %r'
1177
raise errors.BzrCheckError('Reference to missing compression parents %r'
1115
1178
% (missing_text_keys,))
1116
1179
self._log_copied_texts()
1220
1283
# XXX: the following may want to be a class, to pack with a given
1222
mutter('Auto-packing repository %s, which has %d pack files, '
1223
'containing %d revisions into %d packs.', self, total_packs,
1224
total_revisions, self._max_pack_count(total_revisions))
1225
1285
# determine which packs need changing
1226
1286
pack_distribution = self.pack_distribution(total_revisions)
1227
1287
existing_packs = []
1242
1302
existing_packs.append((revision_count, pack))
1243
1303
pack_operations = self.plan_autopack_combinations(
1244
1304
existing_packs, pack_distribution)
1305
num_new_packs = len(pack_operations)
1306
num_old_packs = sum([len(po[1]) for po in pack_operations])
1307
num_revs_affected = sum([po[0] for po in pack_operations])
1308
mutter('Auto-packing repository %s, which has %d pack files, '
1309
'containing %d revisions. Packing %d files into %d affecting %d'
1310
' revisions', self, total_packs, total_revisions, num_old_packs,
1311
num_new_packs, num_revs_affected)
1245
1312
self._execute_pack_operations(pack_operations)
1515
1582
self._packs_by_name = {}
1516
1583
self._packs_at_load = None
1518
def _make_index_map(self, index_suffix):
1519
"""Return information on existing indices.
1521
:param suffix: Index suffix added to pack name.
1523
:returns: (pack_map, indices) where indices is a list of GraphIndex
1524
objects, and pack_map is a mapping from those objects to the
1525
pack tuple they describe.
1527
# TODO: stop using this; it creates new indices unnecessarily.
1528
self.ensure_loaded()
1529
suffix_map = {'.rix': 'revision_index',
1530
'.six': 'signature_index',
1531
'.iix': 'inventory_index',
1532
'.tix': 'text_index',
1534
return self._packs_list_to_pack_map_and_index_list(self.all_packs(),
1535
suffix_map[index_suffix])
1537
def _packs_list_to_pack_map_and_index_list(self, packs, index_attribute):
1538
"""Convert a list of packs to an index pack map and index list.
1540
:param packs: The packs list to process.
1541
:param index_attribute: The attribute that the desired index is found
1543
:return: A tuple (map, list) where map contains the dict from
1544
index:pack_tuple, and lsit contains the indices in the same order
1550
index = getattr(pack, index_attribute)
1551
indices.append(index)
1552
pack_map[index] = (pack.pack_transport, pack.file_name())
1553
return pack_map, indices
1555
def _index_contents(self, pack_map, key_filter=None):
1556
"""Get an iterable of the index contents from a pack_map.
1558
:param pack_map: A map from indices to pack details.
1559
:param key_filter: An optional filter to limit the
1562
indices = [index for index in pack_map.iterkeys()]
1563
all_index = CombinedGraphIndex(indices)
1564
if key_filter is None:
1565
return all_index.iter_all_entries()
1567
return all_index.iter_entries(key_filter)
1569
1585
def _unlock_names(self):
1570
1586
"""Release the mutex around the pack-names index."""
1571
1587
self.repo.control_files.unlock()
1708
1724
# Do not permit preparation for writing if we're not in a 'write lock'.
1709
1725
if not self.repo.is_write_locked():
1710
1726
raise errors.NotWriteLocked(self)
1711
self._new_pack = NewPack(self._upload_transport, self._index_transport,
1712
self._pack_transport, upload_suffix='.pack',
1713
file_mode=self.repo.bzrdir._get_file_mode(),
1714
index_builder_class=self._index_builder_class,
1715
index_class=self._index_class)
1727
self._new_pack = NewPack(self, upload_suffix='.pack',
1728
file_mode=self.repo.bzrdir._get_file_mode())
1716
1729
# allow writing: queue writes to a new index
1717
1730
self.revision_index.add_writable_index(self._new_pack.revision_index,
1718
1731
self._new_pack)
1736
1749
self._new_pack.abort()
1751
# XXX: If we aborted while in the middle of finishing the write
1752
# group, _remove_pack_indices can fail because the indexes are
1753
# already gone. If they're not there we shouldn't fail in this
1754
# case. -- mbp 20081113
1738
1755
self._remove_pack_indices(self._new_pack)
1739
1756
self._new_pack = None
1740
1757
self.repo._text_knit = None
2296
2313
return xml7.serializer_v7
2298
2315
def _get_matching_bzrdir(self):
2299
return bzrdir.format_registry.make_bzrdir(
2316
matching = bzrdir.format_registry.make_bzrdir(
2300
2317
'1.6.1-rich-root')
2318
matching.repository_format = self
2302
2321
def _ignore_setting_bzrdir(self, format):