~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: Frank Aspell
  • Date: 2009-02-17 11:40:05 UTC
  • mto: (4054.1.1 doc)
  • mto: This revision was merged to the branch mainline in revision 4056.
  • Revision ID: frankaspell@googlemail.com-20090217114005-ojufrp6rqht664um
Fixed typos.

Fixed some typos in bzr doc's using "aspell -l en -c FILENAME".

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
16
16
 
 
17
import sys
 
18
 
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)
174
176
 
175
 
    def _external_compression_parents_of_texts(self):
176
 
        keys = set()
177
 
        refs = set()
178
 
        for node in self.text_index.iter_all_entries():
179
 
            keys.add(node[1])
180
 
            refs.update(node[3][1])
181
 
        return refs - keys
182
 
 
183
177
 
184
178
class ExistingPack(Pack):
185
179
    """An in memory proxy for an existing .pack and its disk indices."""
222
216
        'signature': ('.six', 3),
223
217
        }
224
218
 
225
 
    def __init__(self, upload_transport, index_transport, pack_transport,
226
 
        upload_suffix='', file_mode=None, index_builder_class=None,
227
 
        index_class=None):
 
219
    def __init__(self, pack_collection, upload_suffix='', file_mode=None):
228
220
        """Create a NewPack instance.
229
221
 
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
243
 
            object to use.
 
225
        :param file_mode: Unix permissions for newly created file.
244
226
        """
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),
259
242
            # listing.
260
243
            index_builder_class(reference_lists=0),
261
244
            )
 
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.
334
318
        else:
335
319
            raise AssertionError(self._state)
336
320
 
 
321
    def _check_references(self):
 
322
        """Make sure our external references are present.
 
323
        
 
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>)
 
328
        """
 
329
        missing_items = {}
 
330
        for (index_name, external_refs, index) in [
 
331
            ('texts',
 
332
                self.text_index._external_references(),
 
333
                self._pack_collection.text_index.combined_index),
 
334
            ('inventories',
 
335
                self.inventory_index._external_references(),
 
336
                self._pack_collection.inventory_index.combined_index),
 
337
            ]:
 
338
            missing = external_refs.difference(
 
339
                k for (idx, k, v, r) in 
 
340
                index.iter_entries(external_refs))
 
341
            if missing:
 
342
                missing_items[index_name] = sorted(list(missing))
 
343
        if missing_items:
 
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)))
 
349
 
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()
359
373
        # write indices
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
452
466
    # XXX: Probably 'can be written to' could/should be separated from 'acts
453
467
    # like a knit index' -- mbp 20071024
454
468
 
455
 
    def __init__(self):
456
 
        """Create an AggregateIndex."""
 
469
    def __init__(self, reload_func=None):
 
470
        """Create an AggregateIndex.
 
471
 
 
472
        :param reload_func: A function to call if we find we are missing an
 
473
            index. Should have the form reload_func() => True if the list of
 
474
            active pack files has changed.
 
475
        """
 
476
        self._reload_func = reload_func
457
477
        self.index_to_pack = {}
458
 
        self.combined_index = CombinedGraphIndex([])
459
 
        self.data_access = _DirectPackAccess(self.index_to_pack)
 
478
        self.combined_index = CombinedGraphIndex([], reload_func=reload_func)
 
479
        self.data_access = _DirectPackAccess(self.index_to_pack,
 
480
                                             reload_func=reload_func)
460
481
        self.add_callback = None
461
482
 
462
483
    def replace_indices(self, index_to_pack, indices):
536
557
class Packer(object):
537
558
    """Create a pack from packs."""
538
559
 
539
 
    def __init__(self, pack_collection, packs, suffix, revision_ids=None):
 
560
    def __init__(self, pack_collection, packs, suffix, revision_ids=None,
 
561
                 reload_func=None):
540
562
        """Create a Packer.
541
563
 
542
564
        :param pack_collection: A RepositoryPackCollection object where the
544
566
        :param packs: The packs to combine.
545
567
        :param suffix: The suffix to use on the temporary files for the pack.
546
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
547
572
        """
548
573
        self.packs = packs
549
574
        self.suffix = suffix
551
576
        # The pack object we are creating.
552
577
        self.new_pack = None
553
578
        self._pack_collection = pack_collection
 
579
        self._reload_func = reload_func
554
580
        # The index layer keys for the revisions being copied. None for 'all
555
581
        # objects'.
556
582
        self._revision_keys = None
562
588
    def _extra_init(self):
563
589
        """A template hook to allow extending the constructor trivially."""
564
590
 
 
591
    def _pack_map_and_index_list(self, index_attribute):
 
592
        """Convert a list of packs to an index pack map and index list.
 
593
 
 
594
        :param index_attribute: The attribute that the desired index is found
 
595
            on.
 
596
        :return: A tuple (map, list) where map contains the dict from
 
597
            index:pack_tuple, and list contains the indices in the preferred
 
598
            access order.
 
599
        """
 
600
        indices = []
 
601
        pack_map = {}
 
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
 
607
 
 
608
    def _index_contents(self, indices, key_filter=None):
 
609
        """Get an iterable of the index contents from a pack_map.
 
610
 
 
611
        :param indices: The list of indices to query
 
612
        :param key_filter: An optional filter to limit the keys returned.
 
613
        """
 
614
        all_index = CombinedGraphIndex(indices)
 
615
        if key_filter is None:
 
616
            return all_index.iter_all_entries()
 
617
        else:
 
618
            return all_index.iter_entries(key_filter)
 
619
 
565
620
    def pack(self, pb=None):
566
621
        """Create a new pack by reading data from other packs.
567
622
 
581
636
        # XXX: - duplicate code warning with start_write_group; fix before
582
637
        #      considering 'done'.
583
638
        if self._pack_collection._new_pack is not None:
584
 
            raise errors.BzrError('call to create_pack_from_packs while '
585
 
                'another pack is being written.')
 
639
            raise errors.BzrError('call to %s.pack() while another pack is'
 
640
                                  ' being written.'
 
641
                                  % (self.__class__.__name__,))
586
642
        if self.revision_ids is not None:
587
643
            if len(self.revision_ids) == 0:
588
644
                # silly fetch request.
603
659
 
604
660
    def open_pack(self):
605
661
        """Open a pack for the pack we are creating."""
606
 
        return NewPack(self._pack_collection._upload_transport,
607
 
            self._pack_collection._index_transport,
608
 
            self._pack_collection._pack_transport, upload_suffix=self.suffix,
609
 
            file_mode=self._pack_collection.repo.bzrdir._get_file_mode(),
610
 
            index_builder_class=self._pack_collection._index_builder_class,
611
 
            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())
 
664
 
 
665
    def _update_pack_order(self, entries, index_to_pack_map):
 
666
        """Determine how we want our packs to be ordered.
 
667
 
 
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'.
 
672
 
 
673
        :param entries: A list of (index, ...) tuples
 
674
        :param index_to_pack_map: A mapping from index objects to pack objects.
 
675
        """
 
676
        packs = []
 
677
        seen_indexes = set()
 
678
        for entry in entries:
 
679
            index = entry[0]
 
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.')
 
686
            return
 
687
        seen_packs = set(packs)
 
688
        for pack in self.packs:
 
689
            if pack not in seen_packs:
 
690
                packs.append(pack)
 
691
                seen_packs.add(pack)
 
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)
 
697
        self.packs = packs
612
698
 
613
699
    def _copy_revision_texts(self):
614
700
        """Copy revision data to the new pack."""
618
704
        else:
619
705
            revision_keys = None
620
706
        # select revision keys
621
 
        revision_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
622
 
            self.packs, 'revision_index')[0]
623
 
        revision_nodes = self._pack_collection._index_contents(revision_index_map, revision_keys)
 
707
        revision_index_map, revision_indices = self._pack_map_and_index_list(
 
708
            'revision_index')
 
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)
624
712
        # copy revision keys and adjust values
625
713
        self.pb.update("Copying revision texts", 1)
626
714
        total_items, readv_group_iter = self._revision_node_readv(revision_nodes)
646
734
        # querying for keys here could introduce a bug where an inventory item
647
735
        # is missed, so do not change it to query separately without cross
648
736
        # checking like the text key check below.
649
 
        inventory_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
650
 
            self.packs, 'inventory_index')[0]
651
 
        inv_nodes = self._pack_collection._index_contents(inventory_index_map, inv_keys)
 
737
        inventory_index_map, inventory_indices = self._pack_map_and_index_list(
 
738
            'inventory_index')
 
739
        inv_nodes = self._index_contents(inventory_indices, inv_keys)
652
740
        # copy inventory keys and adjust values
653
741
        # XXX: Should be a helper function to allow different inv representation
654
742
        # at this point.
698
786
            self.new_pack.text_index, readv_group_iter, total_items))
699
787
        self._log_copied_texts()
700
788
 
701
 
    def _check_references(self):
702
 
        """Make sure our external refereneces are present."""
703
 
        external_refs = self.new_pack._external_compression_parents_of_texts()
704
 
        if external_refs:
705
 
            index = self._pack_collection.text_index.combined_index
706
 
            found_items = list(index.iter_entries(external_refs))
707
 
            if len(found_items) != len(external_refs):
708
 
                found_keys = set(k for idx, k, refs, value in found_items)
709
 
                missing_items = external_refs - found_keys
710
 
                missing_file_id, missing_revision_id = missing_items.pop()
711
 
                raise errors.RevisionNotPresent(missing_revision_id,
712
 
                                                missing_file_id)
713
 
 
714
789
    def _create_pack_from_packs(self):
715
790
        self.pb.update("Opening pack", 0, 5)
716
791
        self.new_pack = self.open_pack()
734
809
        self._copy_text_texts()
735
810
        # select signature keys
736
811
        signature_filter = self._revision_keys # same keyspace
737
 
        signature_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
738
 
            self.packs, 'signature_index')[0]
739
 
        signature_nodes = self._pack_collection._index_contents(signature_index_map,
 
812
        signature_index_map, signature_indices = self._pack_map_and_index_list(
 
813
            'signature_index')
 
814
        signature_nodes = self._index_contents(signature_indices,
740
815
            signature_filter)
741
816
        # copy signature keys and adjust values
742
817
        self.pb.update("Copying signature texts", 4)
747
822
                time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
748
823
                new_pack.signature_index.key_count(),
749
824
                time.time() - new_pack.start_time)
750
 
        self._check_references()
 
825
        new_pack._check_references()
751
826
        if not self._use_pack(new_pack):
752
827
            new_pack.abort()
753
828
            return None
792
867
            # linear scan up the pack
793
868
            pack_readv_requests.sort()
794
869
            # copy the data
795
 
            transport, path = index_map[index]
796
 
            reader = pack.make_readv_reader(transport, path,
797
 
                [offset[0:2] for offset in pack_readv_requests])
 
870
            pack_obj = index_map[index]
 
871
            transport, path = pack_obj.access_tuple()
 
872
            try:
 
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:
 
877
                    self._reload_func()
 
878
                raise
798
879
            for (names, read_func), (_1, _2, (key, eol_flag)) in \
799
880
                izip(reader.iter_records(), pack_readv_requests):
800
881
                raw_data = read_func(None)
836
917
        pb.update("Copied record", record_index, total_items)
837
918
        for index, readv_vector, node_vector in readv_group_iter:
838
919
            # copy the data
839
 
            transport, path = index_map[index]
840
 
            reader = pack.make_readv_reader(transport, path, readv_vector)
 
920
            pack_obj = index_map[index]
 
921
            transport, path = pack_obj.access_tuple()
 
922
            try:
 
923
                reader = pack.make_readv_reader(transport, path, readv_vector)
 
924
            except errors.NoSuchFile:
 
925
                if self._reload_func is not None:
 
926
                    self._reload_func()
 
927
                raise
841
928
            for (names, read_func), (key, eol_flag, references) in \
842
929
                izip(reader.iter_records(), node_vector):
843
930
                raw_data = read_func(None)
860
947
                record_index += 1
861
948
 
862
949
    def _get_text_nodes(self):
863
 
        text_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
864
 
            self.packs, 'text_index')[0]
865
 
        return text_index_map, self._pack_collection._index_contents(text_index_map,
 
950
        text_index_map, text_indices = self._pack_map_and_index_list(
 
951
            'text_index')
 
952
        return text_index_map, self._index_contents(text_indices,
866
953
            self._text_filter)
867
954
 
868
955
    def _least_readv_node_readv(self, nodes):
971
1058
        # TODO: combine requests in the same index that are in ascending order.
972
1059
        return total, requests
973
1060
 
 
1061
    def open_pack(self):
 
1062
        """Open a pack for the pack we are creating."""
 
1063
        new_pack = super(OptimisingPacker, self).open_pack()
 
1064
        # Turn on the optimization flags for all the index builders.
 
1065
        new_pack.revision_index.set_optimize(for_size=True)
 
1066
        new_pack.inventory_index.set_optimize(for_size=True)
 
1067
        new_pack.text_index.set_optimize(for_size=True)
 
1068
        new_pack.signature_index.set_optimize(for_size=True)
 
1069
        return new_pack
 
1070
 
974
1071
 
975
1072
class ReconcilePacker(Packer):
976
1073
    """A packer which regenerates indices etc as it copies.
1093
1190
            output_texts.add_lines(key, parent_keys, text_lines,
1094
1191
                random_id=True, check_content=False)
1095
1192
        # 5) check that nothing inserted has a reference outside the keyspace.
1096
 
        missing_text_keys = self.new_pack._external_compression_parents_of_texts()
 
1193
        missing_text_keys = self.new_pack.text_index._external_references()
1097
1194
        if missing_text_keys:
1098
 
            raise errors.BzrError('Reference to missing compression parents %r'
 
1195
            raise errors.BzrCheckError('Reference to missing compression parents %r'
1099
1196
                % (missing_text_keys,))
1100
1197
        self._log_copied_texts()
1101
1198
 
1149
1246
        # when a pack is being created by this object, the state of that pack.
1150
1247
        self._new_pack = None
1151
1248
        # aggregated revision index data
1152
 
        self.revision_index = AggregateIndex()
1153
 
        self.inventory_index = AggregateIndex()
1154
 
        self.text_index = AggregateIndex()
1155
 
        self.signature_index = AggregateIndex()
 
1249
        self.revision_index = AggregateIndex(self.reload_pack_names)
 
1250
        self.inventory_index = AggregateIndex(self.reload_pack_names)
 
1251
        self.text_index = AggregateIndex(self.reload_pack_names)
 
1252
        self.signature_index = AggregateIndex(self.reload_pack_names)
1156
1253
 
1157
1254
    def add_pack_to_memory(self, pack):
1158
1255
        """Make a Pack object available to the repository to satisfy queries.
1196
1293
 
1197
1294
        :return: True if packing took place.
1198
1295
        """
 
1296
        while True:
 
1297
            try:
 
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.
 
1302
                pass
 
1303
 
 
1304
    def _do_autopack(self):
1199
1305
        # XXX: Should not be needed when the management of indices is sane.
1200
1306
        total_revisions = self.revision_index.combined_index.key_count()
1201
1307
        total_packs = len(self._names)
1203
1309
            return False
1204
1310
        # XXX: the following may want to be a class, to pack with a given
1205
1311
        # policy.
1206
 
        mutter('Auto-packing repository %s, which has %d pack files, '
1207
 
            'containing %d revisions into %d packs.', self, total_packs,
1208
 
            total_revisions, self._max_pack_count(total_revisions))
1209
1312
        # determine which packs need changing
1210
1313
        pack_distribution = self.pack_distribution(total_revisions)
1211
1314
        existing_packs = []
1226
1329
            existing_packs.append((revision_count, pack))
1227
1330
        pack_operations = self.plan_autopack_combinations(
1228
1331
            existing_packs, pack_distribution)
1229
 
        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)
1230
1341
        return True
1231
1342
 
1232
 
    def _execute_pack_operations(self, pack_operations, _packer_class=Packer):
 
1343
    def _execute_pack_operations(self, pack_operations, _packer_class=Packer,
 
1344
                                 reload_func=None):
1233
1345
        """Execute a series of pack operations.
1234
1346
 
1235
1347
        :param pack_operations: A list of [revision_count, packs_to_combine].
1240
1352
            # we may have no-ops from the setup logic
1241
1353
            if len(packs) == 0:
1242
1354
                continue
1243
 
            _packer_class(self, packs, '.autopack').pack()
 
1355
            packer = _packer_class(self, packs, '.autopack',
 
1356
                                   reload_func=reload_func)
 
1357
            try:
 
1358
                packer.pack()
 
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()
 
1366
                raise
1244
1367
            for pack in packs:
1245
1368
                self._remove_pack_from_memory(pack)
1246
1369
        # record the newly available packs and stop advertising the old
1469
1592
        self._names.pop(pack.name)
1470
1593
        self._packs_by_name.pop(pack.name)
1471
1594
        self._remove_pack_indices(pack)
 
1595
        self.packs.remove(pack)
1472
1596
 
1473
1597
    def _remove_pack_indices(self, pack):
1474
1598
        """Remove the indices for pack from the aggregated indices."""
1498
1622
        self._packs_by_name = {}
1499
1623
        self._packs_at_load = None
1500
1624
 
1501
 
    def _make_index_map(self, index_suffix):
1502
 
        """Return information on existing indices.
1503
 
 
1504
 
        :param suffix: Index suffix added to pack name.
1505
 
 
1506
 
        :returns: (pack_map, indices) where indices is a list of GraphIndex 
1507
 
        objects, and pack_map is a mapping from those objects to the 
1508
 
        pack tuple they describe.
1509
 
        """
1510
 
        # TODO: stop using this; it creates new indices unnecessarily.
1511
 
        self.ensure_loaded()
1512
 
        suffix_map = {'.rix': 'revision_index',
1513
 
            '.six': 'signature_index',
1514
 
            '.iix': 'inventory_index',
1515
 
            '.tix': 'text_index',
1516
 
        }
1517
 
        return self._packs_list_to_pack_map_and_index_list(self.all_packs(),
1518
 
            suffix_map[index_suffix])
1519
 
 
1520
 
    def _packs_list_to_pack_map_and_index_list(self, packs, index_attribute):
1521
 
        """Convert a list of packs to an index pack map and index list.
1522
 
 
1523
 
        :param packs: The packs list to process.
1524
 
        :param index_attribute: The attribute that the desired index is found
1525
 
            on.
1526
 
        :return: A tuple (map, list) where map contains the dict from
1527
 
            index:pack_tuple, and lsit contains the indices in the same order
1528
 
            as the packs list.
1529
 
        """
1530
 
        indices = []
1531
 
        pack_map = {}
1532
 
        for pack in packs:
1533
 
            index = getattr(pack, index_attribute)
1534
 
            indices.append(index)
1535
 
            pack_map[index] = (pack.pack_transport, pack.file_name())
1536
 
        return pack_map, indices
1537
 
 
1538
 
    def _index_contents(self, pack_map, key_filter=None):
1539
 
        """Get an iterable of the index contents from a pack_map.
1540
 
 
1541
 
        :param pack_map: A map from indices to pack details.
1542
 
        :param key_filter: An optional filter to limit the
1543
 
            keys returned.
1544
 
        """
1545
 
        indices = [index for index in pack_map.iterkeys()]
1546
 
        all_index = CombinedGraphIndex(indices)
1547
 
        if key_filter is None:
1548
 
            return all_index.iter_all_entries()
1549
 
        else:
1550
 
            return all_index.iter_entries(key_filter)
1551
 
 
1552
1625
    def _unlock_names(self):
1553
1626
        """Release the mutex around the pack-names index."""
1554
1627
        self.repo.control_files.unlock()
1555
1628
 
1556
 
    def _save_pack_names(self, clear_obsolete_packs=False):
1557
 
        """Save the list of packs.
1558
 
 
1559
 
        This will take out the mutex around the pack names list for the
1560
 
        duration of the method call. If concurrent updates have been made, a
1561
 
        three-way merge between the current list and the current in memory list
1562
 
        is performed.
1563
 
 
1564
 
        :param clear_obsolete_packs: If True, clear out the contents of the
1565
 
            obsolete_packs directory.
1566
 
        """
1567
 
        self.lock_names()
1568
 
        try:
1569
 
            builder = self._index_builder_class()
1570
 
            # load the disk nodes across
1571
 
            disk_nodes = set()
1572
 
            for index, key, value in self._iter_disk_pack_index():
1573
 
                disk_nodes.add((key, value))
1574
 
            # do a two-way diff against our original content
1575
 
            current_nodes = set()
1576
 
            for name, sizes in self._names.iteritems():
1577
 
                current_nodes.add(
1578
 
                    ((name, ), ' '.join(str(size) for size in sizes)))
1579
 
            deleted_nodes = self._packs_at_load - current_nodes
1580
 
            new_nodes = current_nodes - self._packs_at_load
1581
 
            disk_nodes.difference_update(deleted_nodes)
1582
 
            disk_nodes.update(new_nodes)
1583
 
            # TODO: handle same-name, index-size-changes here - 
1584
 
            # e.g. use the value from disk, not ours, *unless* we're the one
1585
 
            # changing it.
1586
 
            for key, value in disk_nodes:
1587
 
                builder.add_node(key, value)
1588
 
            self.transport.put_file('pack-names', builder.finish(),
1589
 
                mode=self.repo.bzrdir._get_file_mode())
1590
 
            # move the baseline forward
1591
 
            self._packs_at_load = disk_nodes
1592
 
            if clear_obsolete_packs:
1593
 
                self._clear_obsolete_packs()
1594
 
        finally:
1595
 
            self._unlock_names()
1596
 
        # synchronise the memory packs list with what we just wrote:
 
1629
    def _diff_pack_names(self):
 
1630
        """Read the pack names from disk, and compare it to the one in memory.
 
1631
 
 
1632
        :return: (disk_nodes, deleted_nodes, new_nodes)
 
1633
            disk_nodes    The final set of nodes that should be referenced
 
1634
            deleted_nodes Nodes which have been removed from when we started
 
1635
            new_nodes     Nodes that are newly introduced
 
1636
        """
 
1637
        # load the disk nodes across
 
1638
        disk_nodes = set()
 
1639
        for index, key, value in self._iter_disk_pack_index():
 
1640
            disk_nodes.add((key, value))
 
1641
 
 
1642
        # do a two-way diff against our original content
 
1643
        current_nodes = set()
 
1644
        for name, sizes in self._names.iteritems():
 
1645
            current_nodes.add(
 
1646
                ((name, ), ' '.join(str(size) for size in sizes)))
 
1647
 
 
1648
        # Packs no longer present in the repository, which were present when we
 
1649
        # locked the repository
 
1650
        deleted_nodes = self._packs_at_load - current_nodes
 
1651
        # Packs which this process is adding
 
1652
        new_nodes = current_nodes - self._packs_at_load
 
1653
 
 
1654
        # Update the disk_nodes set to include the ones we are adding, and
 
1655
        # remove the ones which were removed by someone else
 
1656
        disk_nodes.difference_update(deleted_nodes)
 
1657
        disk_nodes.update(new_nodes)
 
1658
 
 
1659
        return disk_nodes, deleted_nodes, new_nodes
 
1660
 
 
1661
    def _syncronize_pack_names_from_disk_nodes(self, disk_nodes):
 
1662
        """Given the correct set of pack files, update our saved info.
 
1663
 
 
1664
        :return: (removed, added, modified)
 
1665
            removed     pack names removed from self._names
 
1666
            added       pack names added to self._names
 
1667
            modified    pack names that had changed value
 
1668
        """
 
1669
        removed = []
 
1670
        added = []
 
1671
        modified = []
 
1672
        ## self._packs_at_load = disk_nodes
1597
1673
        new_names = dict(disk_nodes)
1598
1674
        # drop no longer present nodes
1599
1675
        for pack in self.all_packs():
1600
1676
            if (pack.name,) not in new_names:
 
1677
                removed.append(pack.name)
1601
1678
                self._remove_pack_from_memory(pack)
1602
1679
        # add new nodes/refresh existing ones
1603
1680
        for key, value in disk_nodes:
1617
1694
                    self._remove_pack_from_memory(self.get_pack_by_name(name))
1618
1695
                    self._names[name] = sizes
1619
1696
                    self.get_pack_by_name(name)
 
1697
                    modified.append(name)
1620
1698
            else:
1621
1699
                # new
1622
1700
                self._names[name] = sizes
1623
1701
                self.get_pack_by_name(name)
 
1702
                added.append(name)
 
1703
        return removed, added, modified
 
1704
 
 
1705
    def _save_pack_names(self, clear_obsolete_packs=False):
 
1706
        """Save the list of packs.
 
1707
 
 
1708
        This will take out the mutex around the pack names list for the
 
1709
        duration of the method call. If concurrent updates have been made, a
 
1710
        three-way merge between the current list and the current in memory list
 
1711
        is performed.
 
1712
 
 
1713
        :param clear_obsolete_packs: If True, clear out the contents of the
 
1714
            obsolete_packs directory.
 
1715
        """
 
1716
        self.lock_names()
 
1717
        try:
 
1718
            builder = self._index_builder_class()
 
1719
            disk_nodes, deleted_nodes, new_nodes = self._diff_pack_names()
 
1720
            # TODO: handle same-name, index-size-changes here - 
 
1721
            # e.g. use the value from disk, not ours, *unless* we're the one
 
1722
            # changing it.
 
1723
            for key, value in disk_nodes:
 
1724
                builder.add_node(key, value)
 
1725
            self.transport.put_file('pack-names', builder.finish(),
 
1726
                mode=self.repo.bzrdir._get_file_mode())
 
1727
            # move the baseline forward
 
1728
            self._packs_at_load = disk_nodes
 
1729
            if clear_obsolete_packs:
 
1730
                self._clear_obsolete_packs()
 
1731
        finally:
 
1732
            self._unlock_names()
 
1733
        # synchronise the memory packs list with what we just wrote:
 
1734
        self._syncronize_pack_names_from_disk_nodes(disk_nodes)
 
1735
 
 
1736
    def reload_pack_names(self):
 
1737
        """Sync our pack listing with what is present in the repository.
 
1738
 
 
1739
        This should be called when we find out that something we thought was
 
1740
        present is now missing. This happens when another process re-packs the
 
1741
        repository, etc.
 
1742
        """
 
1743
        # This is functionally similar to _save_pack_names, but we don't write
 
1744
        # out the new value.
 
1745
        disk_nodes, _, _ = self._diff_pack_names()
 
1746
        self._packs_at_load = disk_nodes
 
1747
        (removed, added,
 
1748
         modified) = self._syncronize_pack_names_from_disk_nodes(disk_nodes)
 
1749
        if removed or added or modified:
 
1750
            return True
 
1751
        return False
 
1752
 
 
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
 
1758
            raise
 
1759
        raise errors.RetryAutopack(self.repo, False, sys.exc_info())
1624
1760
 
1625
1761
    def _clear_obsolete_packs(self):
1626
1762
        """Delete everything from the obsolete-packs directory.
1636
1772
        # Do not permit preparation for writing if we're not in a 'write lock'.
1637
1773
        if not self.repo.is_write_locked():
1638
1774
            raise errors.NotWriteLocked(self)
1639
 
        self._new_pack = NewPack(self._upload_transport, self._index_transport,
1640
 
            self._pack_transport, upload_suffix='.pack',
1641
 
            file_mode=self.repo.bzrdir._get_file_mode(),
1642
 
            index_builder_class=self._index_builder_class,
1643
 
            index_class=self._index_class)
 
1775
        self._new_pack = NewPack(self, upload_suffix='.pack',
 
1776
            file_mode=self.repo.bzrdir._get_file_mode())
1644
1777
        # allow writing: queue writes to a new index
1645
1778
        self.revision_index.add_writable_index(self._new_pack.revision_index,
1646
1779
            self._new_pack)
1660
1793
        # FIXME: just drop the transient index.
1661
1794
        # forget what names there are
1662
1795
        if self._new_pack is not None:
1663
 
            self._new_pack.abort()
1664
 
            self._remove_pack_indices(self._new_pack)
1665
 
            self._new_pack = None
 
1796
            try:
 
1797
                self._new_pack.abort()
 
1798
            finally:
 
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
 
1803
                self._remove_pack_indices(self._new_pack)
 
1804
                self._new_pack = None
1666
1805
        self.repo._text_knit = None
1667
1806
 
1668
1807
    def _commit_write_group(self):
2222
2361
        return xml7.serializer_v7
2223
2362
 
2224
2363
    def _get_matching_bzrdir(self):
2225
 
        return bzrdir.format_registry.make_bzrdir(
 
2364
        matching = bzrdir.format_registry.make_bzrdir(
2226
2365
            '1.6.1-rich-root')
 
2366
        matching.repository_format = self
 
2367
        return matching
2227
2368
 
2228
2369
    def _ignore_setting_bzrdir(self, format):
2229
2370
        pass
2244
2385
                " (deprecated)")
2245
2386
 
2246
2387
 
 
2388
class RepositoryFormatKnitPack6(RepositoryFormatPack):
 
2389
    """A repository with stacking and btree indexes,
 
2390
    without rich roots or subtrees.
 
2391
 
 
2392
    This is equivalent to pack-1.6 with B+Tree indices.
 
2393
    """
 
2394
 
 
2395
    repository_class = KnitPackRepository
 
2396
    _commit_builder_class = PackCommitBuilder
 
2397
    supports_external_lookups = True
 
2398
    # What index classes to use
 
2399
    index_builder_class = BTreeBuilder
 
2400
    index_class = BTreeGraphIndex
 
2401
 
 
2402
    @property
 
2403
    def _serializer(self):
 
2404
        return xml5.serializer_v5
 
2405
 
 
2406
    def _get_matching_bzrdir(self):
 
2407
        return bzrdir.format_registry.make_bzrdir('1.9')
 
2408
 
 
2409
    def _ignore_setting_bzrdir(self, format):
 
2410
        pass
 
2411
 
 
2412
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2413
 
 
2414
    def get_format_string(self):
 
2415
        """See RepositoryFormat.get_format_string()."""
 
2416
        return "Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n"
 
2417
 
 
2418
    def get_format_description(self):
 
2419
        """See RepositoryFormat.get_format_description()."""
 
2420
        return "Packs 6 (uses btree indexes, requires bzr 1.9)"
 
2421
 
 
2422
    def check_conversion_target(self, target_format):
 
2423
        pass
 
2424
 
 
2425
 
 
2426
class RepositoryFormatKnitPack6RichRoot(RepositoryFormatPack):
 
2427
    """A repository with rich roots, no subtrees, stacking and btree indexes.
 
2428
 
 
2429
    1.6-rich-root with B+Tree indices.
 
2430
    """
 
2431
 
 
2432
    repository_class = KnitPackRepository
 
2433
    _commit_builder_class = PackRootCommitBuilder
 
2434
    rich_root_data = True
 
2435
    supports_tree_reference = False # no subtrees
 
2436
    supports_external_lookups = True
 
2437
    # What index classes to use
 
2438
    index_builder_class = BTreeBuilder
 
2439
    index_class = BTreeGraphIndex
 
2440
 
 
2441
    @property
 
2442
    def _serializer(self):
 
2443
        return xml6.serializer_v6
 
2444
 
 
2445
    def _get_matching_bzrdir(self):
 
2446
        return bzrdir.format_registry.make_bzrdir(
 
2447
            '1.9-rich-root')
 
2448
 
 
2449
    def _ignore_setting_bzrdir(self, format):
 
2450
        pass
 
2451
 
 
2452
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2453
 
 
2454
    def check_conversion_target(self, target_format):
 
2455
        if not target_format.rich_root_data:
 
2456
            raise errors.BadConversionTarget(
 
2457
                'Does not support rich root data.', target_format)
 
2458
 
 
2459
    def get_format_string(self):
 
2460
        """See RepositoryFormat.get_format_string()."""
 
2461
        return "Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n"
 
2462
 
 
2463
    def get_format_description(self):
 
2464
        return "Packs 6 rich-root (uses btree indexes, requires bzr 1.9)"
 
2465
 
 
2466
 
2247
2467
class RepositoryFormatPackDevelopment2(RepositoryFormatPack):
2248
2468
    """A no-subtrees development repository.
2249
2469