14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
19
from bzrlib.lazy_import import lazy_import
18
20
lazy_import(globals(), """
19
21
from itertools import izip
172
174
"""The text index is the name + .tix."""
173
175
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
178
class ExistingPack(Pack):
185
179
"""An in memory proxy for an existing .pack and its disk indices."""
222
216
'signature': ('.six', 3),
225
def __init__(self, upload_transport, index_transport, pack_transport,
226
upload_suffix='', file_mode=None, index_builder_class=None,
219
def __init__(self, pack_collection, upload_suffix='', file_mode=None):
228
220
"""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').
222
:param pack_collection: A PackCollection into which this is being inserted.
237
223
:param upload_suffix: An optional suffix to be given to any temporary
238
224
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
225
:param file_mode: Unix permissions for newly created file.
245
227
# The relative locations of the packs are constrained, but all are
246
228
# passed in because the caller has them, so as to avoid object churn.
229
index_builder_class = pack_collection._index_builder_class
247
230
Pack.__init__(self,
248
231
# Revisions: parents list, no text compression.
249
232
index_builder_class(reference_lists=1),
260
243
index_builder_class(reference_lists=0),
245
self._pack_collection = pack_collection
262
246
# When we make readonly indices, we need this.
263
self.index_class = index_class
247
self.index_class = pack_collection._index_class
264
248
# where should the new pack be opened
265
self.upload_transport = upload_transport
249
self.upload_transport = pack_collection._upload_transport
266
250
# where are indices written out to
267
self.index_transport = index_transport
251
self.index_transport = pack_collection._index_transport
268
252
# where is the pack renamed to when it is finished?
269
self.pack_transport = pack_transport
253
self.pack_transport = pack_collection._pack_transport
270
254
# What file mode to upload the pack and indices with.
271
255
self._file_mode = file_mode
272
256
# tracks the content written to the .pack file.
335
319
raise AssertionError(self._state)
321
def _check_references(self):
322
"""Make sure our external references are present.
324
Packs are allowed to have deltas whose base is not in the pack, but it
325
must be present somewhere in this collection. It is not allowed to
326
have deltas based on a fallback repository.
327
(See <https://bugs.launchpad.net/bzr/+bug/288751>)
330
for (index_name, external_refs, index) in [
332
self.text_index._external_references(),
333
self._pack_collection.text_index.combined_index),
335
self.inventory_index._external_references(),
336
self._pack_collection.inventory_index.combined_index),
338
missing = external_refs.difference(
339
k for (idx, k, v, r) in
340
index.iter_entries(external_refs))
342
missing_items[index_name] = sorted(list(missing))
344
from pprint import pformat
345
raise errors.BzrCheckError(
346
"Newly created pack file %r has delta references to "
347
"items not in its repository:\n%s"
348
% (self, pformat(missing_items)))
337
350
def data_inserted(self):
338
351
"""True if data has been added to this pack."""
339
352
return bool(self.get_revision_count() or
356
369
if self._buffer[1]:
357
370
self._write_data('', flush=True)
358
371
self.name = self._hash.hexdigest()
372
self._check_references()
360
374
# XXX: It'd be better to write them all to temporary names, then
361
375
# rename them all into place, so that the window when only some are
462
476
self._reload_func = reload_func
463
477
self.index_to_pack = {}
464
478
self.combined_index = CombinedGraphIndex([], reload_func=reload_func)
465
self.data_access = _DirectPackAccess(self.index_to_pack)
479
self.data_access = _DirectPackAccess(self.index_to_pack,
480
reload_func=reload_func)
466
481
self.add_callback = None
468
483
def replace_indices(self, index_to_pack, indices):
542
557
class Packer(object):
543
558
"""Create a pack from packs."""
545
def __init__(self, pack_collection, packs, suffix, revision_ids=None):
560
def __init__(self, pack_collection, packs, suffix, revision_ids=None,
546
562
"""Create a Packer.
548
564
:param pack_collection: A RepositoryPackCollection object where the
550
566
:param packs: The packs to combine.
551
567
:param suffix: The suffix to use on the temporary files for the pack.
552
568
:param revision_ids: Revision ids to limit the pack to.
569
:param reload_func: A function to call if a pack file/index goes
570
missing. The side effect of calling this function should be to
571
update self.packs. See also AggregateIndex
554
573
self.packs = packs
555
574
self.suffix = suffix
557
576
# The pack object we are creating.
558
577
self.new_pack = None
559
578
self._pack_collection = pack_collection
579
self._reload_func = reload_func
560
580
# The index layer keys for the revisions being copied. None for 'all
562
582
self._revision_keys = None
568
588
def _extra_init(self):
569
589
"""A template hook to allow extending the constructor trivially."""
591
def _pack_map_and_index_list(self, index_attribute):
592
"""Convert a list of packs to an index pack map and index list.
594
:param index_attribute: The attribute that the desired index is found
596
:return: A tuple (map, list) where map contains the dict from
597
index:pack_tuple, and list contains the indices in the preferred
602
for pack_obj in self.packs:
603
index = getattr(pack_obj, index_attribute)
604
indices.append(index)
605
pack_map[index] = pack_obj
606
return pack_map, indices
608
def _index_contents(self, indices, key_filter=None):
609
"""Get an iterable of the index contents from a pack_map.
611
:param indices: The list of indices to query
612
:param key_filter: An optional filter to limit the keys returned.
614
all_index = CombinedGraphIndex(indices)
615
if key_filter is None:
616
return all_index.iter_all_entries()
618
return all_index.iter_entries(key_filter)
571
620
def pack(self, pb=None):
572
621
"""Create a new pack by reading data from other packs.
587
636
# XXX: - duplicate code warning with start_write_group; fix before
588
637
# considering 'done'.
589
638
if self._pack_collection._new_pack is not None:
590
raise errors.BzrError('call to create_pack_from_packs while '
591
'another pack is being written.')
639
raise errors.BzrError('call to %s.pack() while another pack is'
641
% (self.__class__.__name__,))
592
642
if self.revision_ids is not None:
593
643
if len(self.revision_ids) == 0:
594
644
# silly fetch request.
610
660
def open_pack(self):
611
661
"""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)
662
return NewPack(self._pack_collection, upload_suffix=self.suffix,
663
file_mode=self._pack_collection.repo.bzrdir._get_file_mode())
665
def _update_pack_order(self, entries, index_to_pack_map):
666
"""Determine how we want our packs to be ordered.
668
This changes the sort order of the self.packs list so that packs unused
669
by 'entries' will be at the end of the list, so that future requests
670
can avoid probing them. Used packs will be at the front of the
671
self.packs list, in the order of their first use in 'entries'.
673
:param entries: A list of (index, ...) tuples
674
:param index_to_pack_map: A mapping from index objects to pack objects.
678
for entry in entries:
680
if index not in seen_indexes:
681
packs.append(index_to_pack_map[index])
682
seen_indexes.add(index)
683
if len(packs) == len(self.packs):
684
if 'pack' in debug.debug_flags:
685
mutter('Not changing pack list, all packs used.')
687
seen_packs = set(packs)
688
for pack in self.packs:
689
if pack not in seen_packs:
692
if 'pack' in debug.debug_flags:
693
old_names = [p.access_tuple()[1] for p in self.packs]
694
new_names = [p.access_tuple()[1] for p in packs]
695
mutter('Reordering packs\nfrom: %s\n to: %s',
696
old_names, new_names)
619
699
def _copy_revision_texts(self):
620
700
"""Copy revision data to the new pack."""
625
705
revision_keys = None
626
706
# 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)
707
revision_index_map, revision_indices = self._pack_map_and_index_list(
709
revision_nodes = self._index_contents(revision_indices, revision_keys)
710
revision_nodes = list(revision_nodes)
711
self._update_pack_order(revision_nodes, revision_index_map)
630
712
# copy revision keys and adjust values
631
713
self.pb.update("Copying revision texts", 1)
632
714
total_items, readv_group_iter = self._revision_node_readv(revision_nodes)
652
734
# querying for keys here could introduce a bug where an inventory item
653
735
# is missed, so do not change it to query separately without cross
654
736
# 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)
737
inventory_index_map, inventory_indices = self._pack_map_and_index_list(
739
inv_nodes = self._index_contents(inventory_indices, inv_keys)
658
740
# copy inventory keys and adjust values
659
741
# XXX: Should be a helper function to allow different inv representation
704
786
self.new_pack.text_index, readv_group_iter, total_items))
705
787
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
789
def _create_pack_from_packs(self):
721
790
self.pb.update("Opening pack", 0, 5)
722
791
self.new_pack = self.open_pack()
740
809
self._copy_text_texts()
741
810
# select signature keys
742
811
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,
812
signature_index_map, signature_indices = self._pack_map_and_index_list(
814
signature_nodes = self._index_contents(signature_indices,
746
815
signature_filter)
747
816
# copy signature keys and adjust values
748
817
self.pb.update("Copying signature texts", 4)
753
822
time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
754
823
new_pack.signature_index.key_count(),
755
824
time.time() - new_pack.start_time)
756
self._check_references()
825
new_pack._check_references()
757
826
if not self._use_pack(new_pack):
798
867
# linear scan up the pack
799
868
pack_readv_requests.sort()
801
transport, path = index_map[index]
802
reader = pack.make_readv_reader(transport, path,
803
[offset[0:2] for offset in pack_readv_requests])
870
pack_obj = index_map[index]
871
transport, path = pack_obj.access_tuple()
873
reader = pack.make_readv_reader(transport, path,
874
[offset[0:2] for offset in pack_readv_requests])
875
except errors.NoSuchFile:
876
if self._reload_func is not None:
804
879
for (names, read_func), (_1, _2, (key, eol_flag)) in \
805
880
izip(reader.iter_records(), pack_readv_requests):
806
881
raw_data = read_func(None)
842
917
pb.update("Copied record", record_index, total_items)
843
918
for index, readv_vector, node_vector in readv_group_iter:
845
transport, path = index_map[index]
846
reader = pack.make_readv_reader(transport, path, readv_vector)
920
pack_obj = index_map[index]
921
transport, path = pack_obj.access_tuple()
923
reader = pack.make_readv_reader(transport, path, readv_vector)
924
except errors.NoSuchFile:
925
if self._reload_func is not None:
847
928
for (names, read_func), (key, eol_flag, references) in \
848
929
izip(reader.iter_records(), node_vector):
849
930
raw_data = read_func(None)
866
947
record_index += 1
868
949
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,
950
text_index_map, text_indices = self._pack_map_and_index_list(
952
return text_index_map, self._index_contents(text_indices,
872
953
self._text_filter)
874
955
def _least_readv_node_readv(self, nodes):
1109
1190
output_texts.add_lines(key, parent_keys, text_lines,
1110
1191
random_id=True, check_content=False)
1111
1192
# 5) check that nothing inserted has a reference outside the keyspace.
1112
missing_text_keys = self.new_pack._external_compression_parents_of_texts()
1193
missing_text_keys = self.new_pack.text_index._external_references()
1113
1194
if missing_text_keys:
1114
raise errors.BzrError('Reference to missing compression parents %r'
1195
raise errors.BzrCheckError('Reference to missing compression parents %r'
1115
1196
% (missing_text_keys,))
1116
1197
self._log_copied_texts()
1213
1294
:return: True if packing took place.
1298
return self._do_autopack()
1299
except errors.RetryAutopack, e:
1300
# If we get a RetryAutopack exception, we should abort the
1301
# current action, and retry.
1304
def _do_autopack(self):
1215
1305
# XXX: Should not be needed when the management of indices is sane.
1216
1306
total_revisions = self.revision_index.combined_index.key_count()
1217
1307
total_packs = len(self._names)
1220
1310
# 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
1312
# determine which packs need changing
1226
1313
pack_distribution = self.pack_distribution(total_revisions)
1227
1314
existing_packs = []
1242
1329
existing_packs.append((revision_count, pack))
1243
1330
pack_operations = self.plan_autopack_combinations(
1244
1331
existing_packs, pack_distribution)
1245
self._execute_pack_operations(pack_operations)
1332
num_new_packs = len(pack_operations)
1333
num_old_packs = sum([len(po[1]) for po in pack_operations])
1334
num_revs_affected = sum([po[0] for po in pack_operations])
1335
mutter('Auto-packing repository %s, which has %d pack files, '
1336
'containing %d revisions. Packing %d files into %d affecting %d'
1337
' revisions', self, total_packs, total_revisions, num_old_packs,
1338
num_new_packs, num_revs_affected)
1339
self._execute_pack_operations(pack_operations,
1340
reload_func=self._restart_autopack)
1248
def _execute_pack_operations(self, pack_operations, _packer_class=Packer):
1343
def _execute_pack_operations(self, pack_operations, _packer_class=Packer,
1249
1345
"""Execute a series of pack operations.
1251
1347
:param pack_operations: A list of [revision_count, packs_to_combine].
1256
1352
# we may have no-ops from the setup logic
1257
1353
if len(packs) == 0:
1259
_packer_class(self, packs, '.autopack').pack()
1355
packer = _packer_class(self, packs, '.autopack',
1356
reload_func=reload_func)
1359
except errors.RetryWithNewPacks:
1360
# An exception is propagating out of this context, make sure
1361
# this packer has cleaned up. Packer() doesn't set its new_pack
1362
# state into the RepositoryPackCollection object, so we only
1363
# have access to it directly here.
1364
if packer.new_pack is not None:
1365
packer.new_pack.abort()
1260
1367
for pack in packs:
1261
1368
self._remove_pack_from_memory(pack)
1262
1369
# record the newly available packs and stop advertising the old
1515
1622
self._packs_by_name = {}
1516
1623
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
1625
def _unlock_names(self):
1570
1626
"""Release the mutex around the pack-names index."""
1571
1627
self.repo.control_files.unlock()
1753
def _restart_autopack(self):
1754
"""Reload the pack names list, and restart the autopack code."""
1755
if not self.reload_pack_names():
1756
# Re-raise the original exception, because something went missing
1757
# and a restart didn't find it
1759
raise errors.RetryAutopack(self.repo, False, sys.exc_info())
1697
1761
def _clear_obsolete_packs(self):
1698
1762
"""Delete everything from the obsolete-packs directory.
1708
1772
# Do not permit preparation for writing if we're not in a 'write lock'.
1709
1773
if not self.repo.is_write_locked():
1710
1774
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)
1775
self._new_pack = NewPack(self, upload_suffix='.pack',
1776
file_mode=self.repo.bzrdir._get_file_mode())
1716
1777
# allow writing: queue writes to a new index
1717
1778
self.revision_index.add_writable_index(self._new_pack.revision_index,
1718
1779
self._new_pack)
1736
1797
self._new_pack.abort()
1799
# XXX: If we aborted while in the middle of finishing the write
1800
# group, _remove_pack_indices can fail because the indexes are
1801
# already gone. If they're not there we shouldn't fail in this
1802
# case. -- mbp 20081113
1738
1803
self._remove_pack_indices(self._new_pack)
1739
1804
self._new_pack = None
1740
1805
self.repo._text_knit = None
2296
2361
return xml7.serializer_v7
2298
2363
def _get_matching_bzrdir(self):
2299
return bzrdir.format_registry.make_bzrdir(
2364
matching = bzrdir.format_registry.make_bzrdir(
2300
2365
'1.6.1-rich-root')
2366
matching.repository_format = self
2302
2369
def _ignore_setting_bzrdir(self, format):
2359
2426
class RepositoryFormatKnitPack6RichRoot(RepositoryFormatPack):
2360
2427
"""A repository with rich roots, no subtrees, stacking and btree indexes.
2362
This format should be retained until the second release after bzr 1.7.
2364
1.6.1-subtree[as it might have been] with B+Tree indices.
2429
1.6-rich-root with B+Tree indices.
2367
2432
repository_class = KnitPackRepository