~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: Andrew Bennetts
  • Date: 2009-07-27 05:24:02 UTC
  • mfrom: (4570 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4573.
  • Revision ID: andrew.bennetts@canonical.com-20090727052402-e3vakc2pnq0y66gm
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
24
24
 
25
25
from bzrlib import (
26
26
    chk_map,
27
 
    cleanup,
28
 
    config,
29
27
    debug,
30
28
    graph,
31
29
    osutils,
32
30
    pack,
33
31
    transactions,
34
 
    tsort,
35
32
    ui,
 
33
    xml5,
 
34
    xml6,
 
35
    xml7,
36
36
    )
37
37
from bzrlib.index import (
38
38
    CombinedGraphIndex,
39
39
    GraphIndexPrefixAdapter,
40
40
    )
 
41
from bzrlib.knit import (
 
42
    KnitPlainFactory,
 
43
    KnitVersionedFiles,
 
44
    _KnitGraphIndex,
 
45
    _DirectPackAccess,
 
46
    )
 
47
from bzrlib import tsort
41
48
""")
42
49
from bzrlib import (
43
 
    btree_index,
 
50
    bzrdir,
44
51
    errors,
45
52
    lockable_files,
46
53
    lockdir,
 
54
    revision as _mod_revision,
47
55
    )
48
56
 
49
 
from bzrlib.decorators import (
50
 
    needs_read_lock,
51
 
    needs_write_lock,
52
 
    only_raises,
53
 
    )
54
 
from bzrlib.lock import LogicalLockResult
 
57
from bzrlib.decorators import needs_write_lock
 
58
from bzrlib.btree_index import (
 
59
    BTreeGraphIndex,
 
60
    BTreeBuilder,
 
61
    )
 
62
from bzrlib.index import (
 
63
    GraphIndex,
 
64
    InMemoryGraphIndex,
 
65
    )
 
66
from bzrlib.repofmt.knitrepo import KnitRepository
55
67
from bzrlib.repository import (
56
 
    _LazyListJoin,
57
 
    MetaDirRepository,
 
68
    CommitBuilder,
 
69
    MetaDirRepositoryFormat,
58
70
    RepositoryFormat,
59
 
    RepositoryWriteLockResult,
60
 
    )
61
 
from bzrlib.vf_repository import (
62
 
    MetaDirVersionedFileRepository,
63
 
    MetaDirVersionedFileRepositoryFormat,
64
 
    VersionedFileCommitBuilder,
65
 
    VersionedFileRootCommitBuilder,
 
71
    RootCommitBuilder,
 
72
    StreamSource,
66
73
    )
67
74
from bzrlib.trace import (
68
75
    mutter,
69
 
    note,
70
76
    warning,
71
77
    )
72
78
 
73
79
 
74
 
class PackCommitBuilder(VersionedFileCommitBuilder):
75
 
    """Subclass of VersionedFileCommitBuilder to add texts with pack semantics.
 
80
class PackCommitBuilder(CommitBuilder):
 
81
    """A subclass of CommitBuilder to add texts with pack semantics.
76
82
 
77
83
    Specifically this uses one knit object rather than one knit object per
78
84
    added text, reducing memory and object pressure.
80
86
 
81
87
    def __init__(self, repository, parents, config, timestamp=None,
82
88
                 timezone=None, committer=None, revprops=None,
83
 
                 revision_id=None, lossy=False):
84
 
        VersionedFileCommitBuilder.__init__(self, repository, parents, config,
 
89
                 revision_id=None):
 
90
        CommitBuilder.__init__(self, repository, parents, config,
85
91
            timestamp=timestamp, timezone=timezone, committer=committer,
86
 
            revprops=revprops, revision_id=revision_id, lossy=lossy)
 
92
            revprops=revprops, revision_id=revision_id)
87
93
        self._file_graph = graph.Graph(
88
94
            repository._pack_collection.text_index.combined_index)
89
95
 
92
98
        return set([key[1] for key in self._file_graph.heads(keys)])
93
99
 
94
100
 
95
 
class PackRootCommitBuilder(VersionedFileRootCommitBuilder):
 
101
class PackRootCommitBuilder(RootCommitBuilder):
96
102
    """A subclass of RootCommitBuilder to add texts with pack semantics.
97
103
 
98
104
    Specifically this uses one knit object rather than one knit object per
101
107
 
102
108
    def __init__(self, repository, parents, config, timestamp=None,
103
109
                 timezone=None, committer=None, revprops=None,
104
 
                 revision_id=None, lossy=False):
105
 
        super(PackRootCommitBuilder, self).__init__(repository, parents,
106
 
            config, timestamp=timestamp, timezone=timezone,
107
 
            committer=committer, revprops=revprops, revision_id=revision_id,
108
 
            lossy=lossy)
 
110
                 revision_id=None):
 
111
        CommitBuilder.__init__(self, repository, parents, config,
 
112
            timestamp=timestamp, timezone=timezone, committer=committer,
 
113
            revprops=revprops, revision_id=revision_id)
109
114
        self._file_graph = graph.Graph(
110
115
            repository._pack_collection.text_index.combined_index)
111
116
 
219
224
        return self.index_name('text', name)
220
225
 
221
226
    def _replace_index_with_readonly(self, index_type):
222
 
        unlimited_cache = False
223
 
        if index_type == 'chk':
224
 
            unlimited_cache = True
225
 
        index = self.index_class(self.index_transport,
226
 
                    self.index_name(index_type, self.name),
227
 
                    self.index_sizes[self.index_offset(index_type)],
228
 
                    unlimited_cache=unlimited_cache)
229
 
        if index_type == 'chk':
230
 
            index._leaf_factory = btree_index._gcchk_factory
231
 
        setattr(self, index_type + '_index', index)
 
227
        setattr(self, index_type + '_index',
 
228
            self.index_class(self.index_transport,
 
229
                self.index_name(index_type, self.name),
 
230
                self.index_sizes[self.index_offset(index_type)]))
232
231
 
233
232
 
234
233
class ExistingPack(Pack):
315
314
        for index_type in index_types:
316
315
            old_name = self.index_name(index_type, self.name)
317
316
            new_name = '../indices/' + old_name
318
 
            self.upload_transport.move(old_name, new_name)
 
317
            self.upload_transport.rename(old_name, new_name)
319
318
            self._replace_index_with_readonly(index_type)
320
319
        new_name = '../packs/' + self.file_name()
321
 
        self.upload_transport.move(self.file_name(), new_name)
 
320
        self.upload_transport.rename(self.file_name(), new_name)
322
321
        self._state = 'finished'
323
322
 
324
323
    def _get_external_refs(self, index):
423
422
        self._writer.begin()
424
423
        # what state is the pack in? (open, finished, aborted)
425
424
        self._state = 'open'
426
 
        # no name until we finish writing the content
427
 
        self.name = None
428
425
 
429
426
    def abort(self):
430
427
        """Cancel creating this pack."""
451
448
            self.signature_index.key_count() or
452
449
            (self.chk_index is not None and self.chk_index.key_count()))
453
450
 
454
 
    def finish_content(self):
455
 
        if self.name is not None:
456
 
            return
457
 
        self._writer.end()
458
 
        if self._buffer[1]:
459
 
            self._write_data('', flush=True)
460
 
        self.name = self._hash.hexdigest()
461
 
 
462
451
    def finish(self, suspend=False):
463
452
        """Finish the new pack.
464
453
 
470
459
         - stores the index size tuple for the pack in the index_sizes
471
460
           attribute.
472
461
        """
473
 
        self.finish_content()
 
462
        self._writer.end()
 
463
        if self._buffer[1]:
 
464
            self._write_data('', flush=True)
 
465
        self.name = self._hash.hexdigest()
474
466
        if not suspend:
475
467
            self._check_references()
476
468
        # write indices
479
471
        # visible is smaller.  On the other hand none will be seen until
480
472
        # they're in the names list.
481
473
        self.index_sizes = [None, None, None, None]
482
 
        self._write_index('revision', self.revision_index, 'revision',
483
 
            suspend)
 
474
        self._write_index('revision', self.revision_index, 'revision', suspend)
484
475
        self._write_index('inventory', self.inventory_index, 'inventory',
485
476
            suspend)
486
477
        self._write_index('text', self.text_index, 'file texts', suspend)
490
481
            self.index_sizes.append(None)
491
482
            self._write_index('chk', self.chk_index,
492
483
                'content hash bytes', suspend)
493
 
        self.write_stream.close(
494
 
            want_fdatasync=self._pack_collection.config_stack.get('repository.fdatasync'))
 
484
        self.write_stream.close()
495
485
        # Note that this will clobber an existing pack with the same name,
496
486
        # without checking for hash collisions. While this is undesirable this
497
487
        # is something that can be rectified in a subsequent release. One way
506
496
        new_name = self.name + '.pack'
507
497
        if not suspend:
508
498
            new_name = '../packs/' + new_name
509
 
        self.upload_transport.move(self.random_name, new_name)
 
499
        self.upload_transport.rename(self.random_name, new_name)
510
500
        self._state = 'finished'
511
501
        if 'pack' in debug.debug_flags:
512
502
            # XXX: size might be interesting?
540
530
            transport = self.upload_transport
541
531
        else:
542
532
            transport = self.index_transport
543
 
        index_tempfile = index.finish()
544
 
        index_bytes = index_tempfile.read()
545
 
        write_stream = transport.open_write_stream(index_name,
546
 
            mode=self._file_mode)
547
 
        write_stream.write(index_bytes)
548
 
        write_stream.close(
549
 
            want_fdatasync=self._pack_collection.config_stack.get('repository.fdatasync'))
550
 
        self.index_sizes[self.index_offset(index_type)] = len(index_bytes)
 
533
        self.index_sizes[self.index_offset(index_type)] = transport.put_file(
 
534
            index_name, index.finish(), mode=self._file_mode)
551
535
        if 'pack' in debug.debug_flags:
552
536
            # XXX: size might be interesting?
553
537
            mutter('%s: create_pack: wrote %s index: %s%s t+%6.3fs',
590
574
                                             flush_func=flush_func)
591
575
        self.add_callback = None
592
576
 
 
577
    def replace_indices(self, index_to_pack, indices):
 
578
        """Replace the current mappings with fresh ones.
 
579
 
 
580
        This should probably not be used eventually, rather incremental add and
 
581
        removal of indices. It has been added during refactoring of existing
 
582
        code.
 
583
 
 
584
        :param index_to_pack: A mapping from index objects to
 
585
            (transport, name) tuples for the pack file data.
 
586
        :param indices: A list of indices.
 
587
        """
 
588
        # refresh the revision pack map dict without replacing the instance.
 
589
        self.index_to_pack.clear()
 
590
        self.index_to_pack.update(index_to_pack)
 
591
        # XXX: API break - clearly a 'replace' method would be good?
 
592
        self.combined_index._indices[:] = indices
 
593
        # the current add nodes callback for the current writable index if
 
594
        # there is one.
 
595
        self.add_callback = None
 
596
 
593
597
    def add_index(self, index, pack):
594
598
        """Add index to the aggregate, which is an index for Pack pack.
595
599
 
602
606
        # expose it to the index map
603
607
        self.index_to_pack[index] = pack.access_tuple()
604
608
        # put it at the front of the linear index list
605
 
        self.combined_index.insert_index(0, index, pack.name)
 
609
        self.combined_index.insert_index(0, index)
606
610
 
607
611
    def add_writable_index(self, index, pack):
608
612
        """Add an index which is able to have data added to it.
628
632
        self.data_access.set_writer(None, None, (None, None))
629
633
        self.index_to_pack.clear()
630
634
        del self.combined_index._indices[:]
631
 
        del self.combined_index._index_names[:]
632
635
        self.add_callback = None
633
636
 
634
 
    def remove_index(self, index):
 
637
    def remove_index(self, index, pack):
635
638
        """Remove index from the indices used to answer queries.
636
639
 
637
640
        :param index: An index from the pack parameter.
 
641
        :param pack: A Pack instance.
638
642
        """
639
643
        del self.index_to_pack[index]
640
 
        pos = self.combined_index._indices.index(index)
641
 
        del self.combined_index._indices[pos]
642
 
        del self.combined_index._index_names[pos]
 
644
        self.combined_index._indices.remove(index)
643
645
        if (self.add_callback is not None and
644
646
            getattr(index, 'add_nodes', None) == self.add_callback):
645
647
            self.add_callback = None
675
677
        # What text keys to copy. None for 'all texts'. This is set by
676
678
        # _copy_inventory_texts
677
679
        self._text_filter = None
 
680
        self._extra_init()
 
681
 
 
682
    def _extra_init(self):
 
683
        """A template hook to allow extending the constructor trivially."""
 
684
 
 
685
    def _pack_map_and_index_list(self, index_attribute):
 
686
        """Convert a list of packs to an index pack map and index list.
 
687
 
 
688
        :param index_attribute: The attribute that the desired index is found
 
689
            on.
 
690
        :return: A tuple (map, list) where map contains the dict from
 
691
            index:pack_tuple, and list contains the indices in the preferred
 
692
            access order.
 
693
        """
 
694
        indices = []
 
695
        pack_map = {}
 
696
        for pack_obj in self.packs:
 
697
            index = getattr(pack_obj, index_attribute)
 
698
            indices.append(index)
 
699
            pack_map[index] = pack_obj
 
700
        return pack_map, indices
 
701
 
 
702
    def _index_contents(self, indices, key_filter=None):
 
703
        """Get an iterable of the index contents from a pack_map.
 
704
 
 
705
        :param indices: The list of indices to query
 
706
        :param key_filter: An optional filter to limit the keys returned.
 
707
        """
 
708
        all_index = CombinedGraphIndex(indices)
 
709
        if key_filter is None:
 
710
            return all_index.iter_all_entries()
 
711
        else:
 
712
            return all_index.iter_entries(key_filter)
678
713
 
679
714
    def pack(self, pb=None):
680
715
        """Create a new pack by reading data from other packs.
691
726
        :return: A Pack object, or None if nothing was copied.
692
727
        """
693
728
        # open a pack - using the same name as the last temporary file
694
 
        # - which has already been flushed, so it's safe.
 
729
        # - which has already been flushed, so its safe.
695
730
        # XXX: - duplicate code warning with start_write_group; fix before
696
731
        #      considering 'done'.
697
732
        if self._pack_collection._new_pack is not None:
729
764
        new_pack.signature_index.set_optimize(combine_backing_indices=False)
730
765
        return new_pack
731
766
 
 
767
    def _update_pack_order(self, entries, index_to_pack_map):
 
768
        """Determine how we want our packs to be ordered.
 
769
 
 
770
        This changes the sort order of the self.packs list so that packs unused
 
771
        by 'entries' will be at the end of the list, so that future requests
 
772
        can avoid probing them.  Used packs will be at the front of the
 
773
        self.packs list, in the order of their first use in 'entries'.
 
774
 
 
775
        :param entries: A list of (index, ...) tuples
 
776
        :param index_to_pack_map: A mapping from index objects to pack objects.
 
777
        """
 
778
        packs = []
 
779
        seen_indexes = set()
 
780
        for entry in entries:
 
781
            index = entry[0]
 
782
            if index not in seen_indexes:
 
783
                packs.append(index_to_pack_map[index])
 
784
                seen_indexes.add(index)
 
785
        if len(packs) == len(self.packs):
 
786
            if 'pack' in debug.debug_flags:
 
787
                mutter('Not changing pack list, all packs used.')
 
788
            return
 
789
        seen_packs = set(packs)
 
790
        for pack in self.packs:
 
791
            if pack not in seen_packs:
 
792
                packs.append(pack)
 
793
                seen_packs.add(pack)
 
794
        if 'pack' in debug.debug_flags:
 
795
            old_names = [p.access_tuple()[1] for p in self.packs]
 
796
            new_names = [p.access_tuple()[1] for p in packs]
 
797
            mutter('Reordering packs\nfrom: %s\n  to: %s',
 
798
                   old_names, new_names)
 
799
        self.packs = packs
 
800
 
732
801
    def _copy_revision_texts(self):
733
802
        """Copy revision data to the new pack."""
734
 
        raise NotImplementedError(self._copy_revision_texts)
 
803
        # select revisions
 
804
        if self.revision_ids:
 
805
            revision_keys = [(revision_id,) for revision_id in self.revision_ids]
 
806
        else:
 
807
            revision_keys = None
 
808
        # select revision keys
 
809
        revision_index_map, revision_indices = self._pack_map_and_index_list(
 
810
            'revision_index')
 
811
        revision_nodes = self._index_contents(revision_indices, revision_keys)
 
812
        revision_nodes = list(revision_nodes)
 
813
        self._update_pack_order(revision_nodes, revision_index_map)
 
814
        # copy revision keys and adjust values
 
815
        self.pb.update("Copying revision texts", 1)
 
816
        total_items, readv_group_iter = self._revision_node_readv(revision_nodes)
 
817
        list(self._copy_nodes_graph(revision_index_map, self.new_pack._writer,
 
818
            self.new_pack.revision_index, readv_group_iter, total_items))
 
819
        if 'pack' in debug.debug_flags:
 
820
            mutter('%s: create_pack: revisions copied: %s%s %d items t+%6.3fs',
 
821
                time.ctime(), self._pack_collection._upload_transport.base,
 
822
                self.new_pack.random_name,
 
823
                self.new_pack.revision_index.key_count(),
 
824
                time.time() - self.new_pack.start_time)
 
825
        self._revision_keys = revision_keys
735
826
 
736
827
    def _copy_inventory_texts(self):
737
828
        """Copy the inventory texts to the new pack.
740
831
 
741
832
        Sets self._text_filter appropriately.
742
833
        """
743
 
        raise NotImplementedError(self._copy_inventory_texts)
 
834
        # select inventory keys
 
835
        inv_keys = self._revision_keys # currently the same keyspace, and note that
 
836
        # querying for keys here could introduce a bug where an inventory item
 
837
        # is missed, so do not change it to query separately without cross
 
838
        # checking like the text key check below.
 
839
        inventory_index_map, inventory_indices = self._pack_map_and_index_list(
 
840
            'inventory_index')
 
841
        inv_nodes = self._index_contents(inventory_indices, inv_keys)
 
842
        # copy inventory keys and adjust values
 
843
        # XXX: Should be a helper function to allow different inv representation
 
844
        # at this point.
 
845
        self.pb.update("Copying inventory texts", 2)
 
846
        total_items, readv_group_iter = self._least_readv_node_readv(inv_nodes)
 
847
        # Only grab the output lines if we will be processing them
 
848
        output_lines = bool(self.revision_ids)
 
849
        inv_lines = self._copy_nodes_graph(inventory_index_map,
 
850
            self.new_pack._writer, self.new_pack.inventory_index,
 
851
            readv_group_iter, total_items, output_lines=output_lines)
 
852
        if self.revision_ids:
 
853
            self._process_inventory_lines(inv_lines)
 
854
        else:
 
855
            # eat the iterator to cause it to execute.
 
856
            list(inv_lines)
 
857
            self._text_filter = None
 
858
        if 'pack' in debug.debug_flags:
 
859
            mutter('%s: create_pack: inventories copied: %s%s %d items t+%6.3fs',
 
860
                time.ctime(), self._pack_collection._upload_transport.base,
 
861
                self.new_pack.random_name,
 
862
                self.new_pack.inventory_index.key_count(),
 
863
                time.time() - self.new_pack.start_time)
744
864
 
745
865
    def _copy_text_texts(self):
746
 
        raise NotImplementedError(self._copy_text_texts)
 
866
        # select text keys
 
867
        text_index_map, text_nodes = self._get_text_nodes()
 
868
        if self._text_filter is not None:
 
869
            # We could return the keys copied as part of the return value from
 
870
            # _copy_nodes_graph but this doesn't work all that well with the
 
871
            # need to get line output too, so we check separately, and as we're
 
872
            # going to buffer everything anyway, we check beforehand, which
 
873
            # saves reading knit data over the wire when we know there are
 
874
            # mising records.
 
875
            text_nodes = set(text_nodes)
 
876
            present_text_keys = set(_node[1] for _node in text_nodes)
 
877
            missing_text_keys = set(self._text_filter) - present_text_keys
 
878
            if missing_text_keys:
 
879
                # TODO: raise a specific error that can handle many missing
 
880
                # keys.
 
881
                mutter("missing keys during fetch: %r", missing_text_keys)
 
882
                a_missing_key = missing_text_keys.pop()
 
883
                raise errors.RevisionNotPresent(a_missing_key[1],
 
884
                    a_missing_key[0])
 
885
        # copy text keys and adjust values
 
886
        self.pb.update("Copying content texts", 3)
 
887
        total_items, readv_group_iter = self._least_readv_node_readv(text_nodes)
 
888
        list(self._copy_nodes_graph(text_index_map, self.new_pack._writer,
 
889
            self.new_pack.text_index, readv_group_iter, total_items))
 
890
        self._log_copied_texts()
747
891
 
748
892
    def _create_pack_from_packs(self):
749
 
        raise NotImplementedError(self._create_pack_from_packs)
 
893
        self.pb.update("Opening pack", 0, 5)
 
894
        self.new_pack = self.open_pack()
 
895
        new_pack = self.new_pack
 
896
        # buffer data - we won't be reading-back during the pack creation and
 
897
        # this makes a significant difference on sftp pushes.
 
898
        new_pack.set_write_cache_size(1024*1024)
 
899
        if 'pack' in debug.debug_flags:
 
900
            plain_pack_list = ['%s%s' % (a_pack.pack_transport.base, a_pack.name)
 
901
                for a_pack in self.packs]
 
902
            if self.revision_ids is not None:
 
903
                rev_count = len(self.revision_ids)
 
904
            else:
 
905
                rev_count = 'all'
 
906
            mutter('%s: create_pack: creating pack from source packs: '
 
907
                '%s%s %s revisions wanted %s t=0',
 
908
                time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
 
909
                plain_pack_list, rev_count)
 
910
        self._copy_revision_texts()
 
911
        self._copy_inventory_texts()
 
912
        self._copy_text_texts()
 
913
        # select signature keys
 
914
        signature_filter = self._revision_keys # same keyspace
 
915
        signature_index_map, signature_indices = self._pack_map_and_index_list(
 
916
            'signature_index')
 
917
        signature_nodes = self._index_contents(signature_indices,
 
918
            signature_filter)
 
919
        # copy signature keys and adjust values
 
920
        self.pb.update("Copying signature texts", 4)
 
921
        self._copy_nodes(signature_nodes, signature_index_map, new_pack._writer,
 
922
            new_pack.signature_index)
 
923
        if 'pack' in debug.debug_flags:
 
924
            mutter('%s: create_pack: revision signatures copied: %s%s %d items t+%6.3fs',
 
925
                time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
 
926
                new_pack.signature_index.key_count(),
 
927
                time.time() - new_pack.start_time)
 
928
        # copy chk contents
 
929
        # NB XXX: how to check CHK references are present? perhaps by yielding
 
930
        # the items? How should that interact with stacked repos?
 
931
        if new_pack.chk_index is not None:
 
932
            self._copy_chks()
 
933
            if 'pack' in debug.debug_flags:
 
934
                mutter('%s: create_pack: chk content copied: %s%s %d items t+%6.3fs',
 
935
                    time.ctime(), self._pack_collection._upload_transport.base,
 
936
                    new_pack.random_name,
 
937
                    new_pack.chk_index.key_count(),
 
938
                    time.time() - new_pack.start_time)
 
939
        new_pack._check_references()
 
940
        if not self._use_pack(new_pack):
 
941
            new_pack.abort()
 
942
            return None
 
943
        self.pb.update("Finishing pack", 5)
 
944
        new_pack.finish()
 
945
        self._pack_collection.allocate(new_pack)
 
946
        return new_pack
 
947
 
 
948
    def _copy_chks(self, refs=None):
 
949
        # XXX: Todo, recursive follow-pointers facility when fetching some
 
950
        # revisions only.
 
951
        chk_index_map, chk_indices = self._pack_map_and_index_list(
 
952
            'chk_index')
 
953
        chk_nodes = self._index_contents(chk_indices, refs)
 
954
        new_refs = set()
 
955
        # TODO: This isn't strictly tasteful as we are accessing some private
 
956
        #       variables (_serializer). Perhaps a better way would be to have
 
957
        #       Repository._deserialise_chk_node()
 
958
        search_key_func = chk_map.search_key_registry.get(
 
959
            self._pack_collection.repo._serializer.search_key_name)
 
960
        def accumlate_refs(lines):
 
961
            # XXX: move to a generic location
 
962
            # Yay mismatch:
 
963
            bytes = ''.join(lines)
 
964
            node = chk_map._deserialise(bytes, ("unknown",), search_key_func)
 
965
            new_refs.update(node.refs())
 
966
        self._copy_nodes(chk_nodes, chk_index_map, self.new_pack._writer,
 
967
            self.new_pack.chk_index, output_lines=accumlate_refs)
 
968
        return new_refs
 
969
 
 
970
    def _copy_nodes(self, nodes, index_map, writer, write_index,
 
971
        output_lines=None):
 
972
        """Copy knit nodes between packs with no graph references.
 
973
 
 
974
        :param output_lines: Output full texts of copied items.
 
975
        """
 
976
        pb = ui.ui_factory.nested_progress_bar()
 
977
        try:
 
978
            return self._do_copy_nodes(nodes, index_map, writer,
 
979
                write_index, pb, output_lines=output_lines)
 
980
        finally:
 
981
            pb.finished()
 
982
 
 
983
    def _do_copy_nodes(self, nodes, index_map, writer, write_index, pb,
 
984
        output_lines=None):
 
985
        # for record verification
 
986
        knit = KnitVersionedFiles(None, None)
 
987
        # plan a readv on each source pack:
 
988
        # group by pack
 
989
        nodes = sorted(nodes)
 
990
        # how to map this into knit.py - or knit.py into this?
 
991
        # we don't want the typical knit logic, we want grouping by pack
 
992
        # at this point - perhaps a helper library for the following code
 
993
        # duplication points?
 
994
        request_groups = {}
 
995
        for index, key, value in nodes:
 
996
            if index not in request_groups:
 
997
                request_groups[index] = []
 
998
            request_groups[index].append((key, value))
 
999
        record_index = 0
 
1000
        pb.update("Copied record", record_index, len(nodes))
 
1001
        for index, items in request_groups.iteritems():
 
1002
            pack_readv_requests = []
 
1003
            for key, value in items:
 
1004
                # ---- KnitGraphIndex.get_position
 
1005
                bits = value[1:].split(' ')
 
1006
                offset, length = int(bits[0]), int(bits[1])
 
1007
                pack_readv_requests.append((offset, length, (key, value[0])))
 
1008
            # linear scan up the pack
 
1009
            pack_readv_requests.sort()
 
1010
            # copy the data
 
1011
            pack_obj = index_map[index]
 
1012
            transport, path = pack_obj.access_tuple()
 
1013
            try:
 
1014
                reader = pack.make_readv_reader(transport, path,
 
1015
                    [offset[0:2] for offset in pack_readv_requests])
 
1016
            except errors.NoSuchFile:
 
1017
                if self._reload_func is not None:
 
1018
                    self._reload_func()
 
1019
                raise
 
1020
            for (names, read_func), (_1, _2, (key, eol_flag)) in \
 
1021
                izip(reader.iter_records(), pack_readv_requests):
 
1022
                raw_data = read_func(None)
 
1023
                # check the header only
 
1024
                if output_lines is not None:
 
1025
                    output_lines(knit._parse_record(key[-1], raw_data)[0])
 
1026
                else:
 
1027
                    df, _ = knit._parse_record_header(key, raw_data)
 
1028
                    df.close()
 
1029
                pos, size = writer.add_bytes_record(raw_data, names)
 
1030
                write_index.add_node(key, eol_flag + "%d %d" % (pos, size))
 
1031
                pb.update("Copied record", record_index)
 
1032
                record_index += 1
 
1033
 
 
1034
    def _copy_nodes_graph(self, index_map, writer, write_index,
 
1035
        readv_group_iter, total_items, output_lines=False):
 
1036
        """Copy knit nodes between packs.
 
1037
 
 
1038
        :param output_lines: Return lines present in the copied data as
 
1039
            an iterator of line,version_id.
 
1040
        """
 
1041
        pb = ui.ui_factory.nested_progress_bar()
 
1042
        try:
 
1043
            for result in self._do_copy_nodes_graph(index_map, writer,
 
1044
                write_index, output_lines, pb, readv_group_iter, total_items):
 
1045
                yield result
 
1046
        except Exception:
 
1047
            # Python 2.4 does not permit try:finally: in a generator.
 
1048
            pb.finished()
 
1049
            raise
 
1050
        else:
 
1051
            pb.finished()
 
1052
 
 
1053
    def _do_copy_nodes_graph(self, index_map, writer, write_index,
 
1054
        output_lines, pb, readv_group_iter, total_items):
 
1055
        # for record verification
 
1056
        knit = KnitVersionedFiles(None, None)
 
1057
        # for line extraction when requested (inventories only)
 
1058
        if output_lines:
 
1059
            factory = KnitPlainFactory()
 
1060
        record_index = 0
 
1061
        pb.update("Copied record", record_index, total_items)
 
1062
        for index, readv_vector, node_vector in readv_group_iter:
 
1063
            # copy the data
 
1064
            pack_obj = index_map[index]
 
1065
            transport, path = pack_obj.access_tuple()
 
1066
            try:
 
1067
                reader = pack.make_readv_reader(transport, path, readv_vector)
 
1068
            except errors.NoSuchFile:
 
1069
                if self._reload_func is not None:
 
1070
                    self._reload_func()
 
1071
                raise
 
1072
            for (names, read_func), (key, eol_flag, references) in \
 
1073
                izip(reader.iter_records(), node_vector):
 
1074
                raw_data = read_func(None)
 
1075
                if output_lines:
 
1076
                    # read the entire thing
 
1077
                    content, _ = knit._parse_record(key[-1], raw_data)
 
1078
                    if len(references[-1]) == 0:
 
1079
                        line_iterator = factory.get_fulltext_content(content)
 
1080
                    else:
 
1081
                        line_iterator = factory.get_linedelta_content(content)
 
1082
                    for line in line_iterator:
 
1083
                        yield line, key
 
1084
                else:
 
1085
                    # check the header only
 
1086
                    df, _ = knit._parse_record_header(key, raw_data)
 
1087
                    df.close()
 
1088
                pos, size = writer.add_bytes_record(raw_data, names)
 
1089
                write_index.add_node(key, eol_flag + "%d %d" % (pos, size), references)
 
1090
                pb.update("Copied record", record_index)
 
1091
                record_index += 1
 
1092
 
 
1093
    def _get_text_nodes(self):
 
1094
        text_index_map, text_indices = self._pack_map_and_index_list(
 
1095
            'text_index')
 
1096
        return text_index_map, self._index_contents(text_indices,
 
1097
            self._text_filter)
 
1098
 
 
1099
    def _least_readv_node_readv(self, nodes):
 
1100
        """Generate request groups for nodes using the least readv's.
 
1101
 
 
1102
        :param nodes: An iterable of graph index nodes.
 
1103
        :return: Total node count and an iterator of the data needed to perform
 
1104
            readvs to obtain the data for nodes. Each item yielded by the
 
1105
            iterator is a tuple with:
 
1106
            index, readv_vector, node_vector. readv_vector is a list ready to
 
1107
            hand to the transport readv method, and node_vector is a list of
 
1108
            (key, eol_flag, references) for the the node retrieved by the
 
1109
            matching readv_vector.
 
1110
        """
 
1111
        # group by pack so we do one readv per pack
 
1112
        nodes = sorted(nodes)
 
1113
        total = len(nodes)
 
1114
        request_groups = {}
 
1115
        for index, key, value, references in nodes:
 
1116
            if index not in request_groups:
 
1117
                request_groups[index] = []
 
1118
            request_groups[index].append((key, value, references))
 
1119
        result = []
 
1120
        for index, items in request_groups.iteritems():
 
1121
            pack_readv_requests = []
 
1122
            for key, value, references in items:
 
1123
                # ---- KnitGraphIndex.get_position
 
1124
                bits = value[1:].split(' ')
 
1125
                offset, length = int(bits[0]), int(bits[1])
 
1126
                pack_readv_requests.append(
 
1127
                    ((offset, length), (key, value[0], references)))
 
1128
            # linear scan up the pack to maximum range combining.
 
1129
            pack_readv_requests.sort()
 
1130
            # split out the readv and the node data.
 
1131
            pack_readv = [readv for readv, node in pack_readv_requests]
 
1132
            node_vector = [node for readv, node in pack_readv_requests]
 
1133
            result.append((index, pack_readv, node_vector))
 
1134
        return total, result
750
1135
 
751
1136
    def _log_copied_texts(self):
752
1137
        if 'pack' in debug.debug_flags:
756
1141
                self.new_pack.text_index.key_count(),
757
1142
                time.time() - self.new_pack.start_time)
758
1143
 
 
1144
    def _process_inventory_lines(self, inv_lines):
 
1145
        """Use up the inv_lines generator and setup a text key filter."""
 
1146
        repo = self._pack_collection.repo
 
1147
        fileid_revisions = repo._find_file_ids_from_xml_inventory_lines(
 
1148
            inv_lines, self.revision_keys)
 
1149
        text_filter = []
 
1150
        for fileid, file_revids in fileid_revisions.iteritems():
 
1151
            text_filter.extend([(fileid, file_revid) for file_revid in file_revids])
 
1152
        self._text_filter = text_filter
 
1153
 
 
1154
    def _revision_node_readv(self, revision_nodes):
 
1155
        """Return the total revisions and the readv's to issue.
 
1156
 
 
1157
        :param revision_nodes: The revision index contents for the packs being
 
1158
            incorporated into the new pack.
 
1159
        :return: As per _least_readv_node_readv.
 
1160
        """
 
1161
        return self._least_readv_node_readv(revision_nodes)
 
1162
 
759
1163
    def _use_pack(self, new_pack):
760
1164
        """Return True if new_pack should be used.
761
1165
 
765
1169
        return new_pack.data_inserted()
766
1170
 
767
1171
 
 
1172
class OptimisingPacker(Packer):
 
1173
    """A packer which spends more time to create better disk layouts."""
 
1174
 
 
1175
    def _revision_node_readv(self, revision_nodes):
 
1176
        """Return the total revisions and the readv's to issue.
 
1177
 
 
1178
        This sort places revisions in topological order with the ancestors
 
1179
        after the children.
 
1180
 
 
1181
        :param revision_nodes: The revision index contents for the packs being
 
1182
            incorporated into the new pack.
 
1183
        :return: As per _least_readv_node_readv.
 
1184
        """
 
1185
        # build an ancestors dict
 
1186
        ancestors = {}
 
1187
        by_key = {}
 
1188
        for index, key, value, references in revision_nodes:
 
1189
            ancestors[key] = references[0]
 
1190
            by_key[key] = (index, value, references)
 
1191
        order = tsort.topo_sort(ancestors)
 
1192
        total = len(order)
 
1193
        # Single IO is pathological, but it will work as a starting point.
 
1194
        requests = []
 
1195
        for key in reversed(order):
 
1196
            index, value, references = by_key[key]
 
1197
            # ---- KnitGraphIndex.get_position
 
1198
            bits = value[1:].split(' ')
 
1199
            offset, length = int(bits[0]), int(bits[1])
 
1200
            requests.append(
 
1201
                (index, [(offset, length)], [(key, value[0], references)]))
 
1202
        # TODO: combine requests in the same index that are in ascending order.
 
1203
        return total, requests
 
1204
 
 
1205
    def open_pack(self):
 
1206
        """Open a pack for the pack we are creating."""
 
1207
        new_pack = super(OptimisingPacker, self).open_pack()
 
1208
        # Turn on the optimization flags for all the index builders.
 
1209
        new_pack.revision_index.set_optimize(for_size=True)
 
1210
        new_pack.inventory_index.set_optimize(for_size=True)
 
1211
        new_pack.text_index.set_optimize(for_size=True)
 
1212
        new_pack.signature_index.set_optimize(for_size=True)
 
1213
        return new_pack
 
1214
 
 
1215
 
 
1216
class ReconcilePacker(Packer):
 
1217
    """A packer which regenerates indices etc as it copies.
 
1218
 
 
1219
    This is used by ``bzr reconcile`` to cause parent text pointers to be
 
1220
    regenerated.
 
1221
    """
 
1222
 
 
1223
    def _extra_init(self):
 
1224
        self._data_changed = False
 
1225
 
 
1226
    def _process_inventory_lines(self, inv_lines):
 
1227
        """Generate a text key reference map rather for reconciling with."""
 
1228
        repo = self._pack_collection.repo
 
1229
        refs = repo._find_text_key_references_from_xml_inventory_lines(
 
1230
            inv_lines)
 
1231
        self._text_refs = refs
 
1232
        # during reconcile we:
 
1233
        #  - convert unreferenced texts to full texts
 
1234
        #  - correct texts which reference a text not copied to be full texts
 
1235
        #  - copy all others as-is but with corrected parents.
 
1236
        #  - so at this point we don't know enough to decide what becomes a full
 
1237
        #    text.
 
1238
        self._text_filter = None
 
1239
 
 
1240
    def _copy_text_texts(self):
 
1241
        """generate what texts we should have and then copy."""
 
1242
        self.pb.update("Copying content texts", 3)
 
1243
        # we have three major tasks here:
 
1244
        # 1) generate the ideal index
 
1245
        repo = self._pack_collection.repo
 
1246
        ancestors = dict([(key[0], tuple(ref[0] for ref in refs[0])) for
 
1247
            _1, key, _2, refs in
 
1248
            self.new_pack.revision_index.iter_all_entries()])
 
1249
        ideal_index = repo._generate_text_key_index(self._text_refs, ancestors)
 
1250
        # 2) generate a text_nodes list that contains all the deltas that can
 
1251
        #    be used as-is, with corrected parents.
 
1252
        ok_nodes = []
 
1253
        bad_texts = []
 
1254
        discarded_nodes = []
 
1255
        NULL_REVISION = _mod_revision.NULL_REVISION
 
1256
        text_index_map, text_nodes = self._get_text_nodes()
 
1257
        for node in text_nodes:
 
1258
            # 0 - index
 
1259
            # 1 - key
 
1260
            # 2 - value
 
1261
            # 3 - refs
 
1262
            try:
 
1263
                ideal_parents = tuple(ideal_index[node[1]])
 
1264
            except KeyError:
 
1265
                discarded_nodes.append(node)
 
1266
                self._data_changed = True
 
1267
            else:
 
1268
                if ideal_parents == (NULL_REVISION,):
 
1269
                    ideal_parents = ()
 
1270
                if ideal_parents == node[3][0]:
 
1271
                    # no change needed.
 
1272
                    ok_nodes.append(node)
 
1273
                elif ideal_parents[0:1] == node[3][0][0:1]:
 
1274
                    # the left most parent is the same, or there are no parents
 
1275
                    # today. Either way, we can preserve the representation as
 
1276
                    # long as we change the refs to be inserted.
 
1277
                    self._data_changed = True
 
1278
                    ok_nodes.append((node[0], node[1], node[2],
 
1279
                        (ideal_parents, node[3][1])))
 
1280
                    self._data_changed = True
 
1281
                else:
 
1282
                    # Reinsert this text completely
 
1283
                    bad_texts.append((node[1], ideal_parents))
 
1284
                    self._data_changed = True
 
1285
        # we're finished with some data.
 
1286
        del ideal_index
 
1287
        del text_nodes
 
1288
        # 3) bulk copy the ok data
 
1289
        total_items, readv_group_iter = self._least_readv_node_readv(ok_nodes)
 
1290
        list(self._copy_nodes_graph(text_index_map, self.new_pack._writer,
 
1291
            self.new_pack.text_index, readv_group_iter, total_items))
 
1292
        # 4) adhoc copy all the other texts.
 
1293
        # We have to topologically insert all texts otherwise we can fail to
 
1294
        # reconcile when parts of a single delta chain are preserved intact,
 
1295
        # and other parts are not. E.g. Discarded->d1->d2->d3. d1 will be
 
1296
        # reinserted, and if d3 has incorrect parents it will also be
 
1297
        # reinserted. If we insert d3 first, d2 is present (as it was bulk
 
1298
        # copied), so we will try to delta, but d2 is not currently able to be
 
1299
        # extracted because it's basis d1 is not present. Topologically sorting
 
1300
        # addresses this. The following generates a sort for all the texts that
 
1301
        # are being inserted without having to reference the entire text key
 
1302
        # space (we only topo sort the revisions, which is smaller).
 
1303
        topo_order = tsort.topo_sort(ancestors)
 
1304
        rev_order = dict(zip(topo_order, range(len(topo_order))))
 
1305
        bad_texts.sort(key=lambda key:rev_order.get(key[0][1], 0))
 
1306
        transaction = repo.get_transaction()
 
1307
        file_id_index = GraphIndexPrefixAdapter(
 
1308
            self.new_pack.text_index,
 
1309
            ('blank', ), 1,
 
1310
            add_nodes_callback=self.new_pack.text_index.add_nodes)
 
1311
        data_access = _DirectPackAccess(
 
1312
                {self.new_pack.text_index:self.new_pack.access_tuple()})
 
1313
        data_access.set_writer(self.new_pack._writer, self.new_pack.text_index,
 
1314
            self.new_pack.access_tuple())
 
1315
        output_texts = KnitVersionedFiles(
 
1316
            _KnitGraphIndex(self.new_pack.text_index,
 
1317
                add_callback=self.new_pack.text_index.add_nodes,
 
1318
                deltas=True, parents=True, is_locked=repo.is_locked),
 
1319
            data_access=data_access, max_delta_chain=200)
 
1320
        for key, parent_keys in bad_texts:
 
1321
            # We refer to the new pack to delta data being output.
 
1322
            # A possible improvement would be to catch errors on short reads
 
1323
            # and only flush then.
 
1324
            self.new_pack.flush()
 
1325
            parents = []
 
1326
            for parent_key in parent_keys:
 
1327
                if parent_key[0] != key[0]:
 
1328
                    # Graph parents must match the fileid
 
1329
                    raise errors.BzrError('Mismatched key parent %r:%r' %
 
1330
                        (key, parent_keys))
 
1331
                parents.append(parent_key[1])
 
1332
            text_lines = osutils.split_lines(repo.texts.get_record_stream(
 
1333
                [key], 'unordered', True).next().get_bytes_as('fulltext'))
 
1334
            output_texts.add_lines(key, parent_keys, text_lines,
 
1335
                random_id=True, check_content=False)
 
1336
        # 5) check that nothing inserted has a reference outside the keyspace.
 
1337
        missing_text_keys = self.new_pack.text_index._external_references()
 
1338
        if missing_text_keys:
 
1339
            raise errors.BzrCheckError('Reference to missing compression parents %r'
 
1340
                % (missing_text_keys,))
 
1341
        self._log_copied_texts()
 
1342
 
 
1343
    def _use_pack(self, new_pack):
 
1344
        """Override _use_pack to check for reconcile having changed content."""
 
1345
        # XXX: we might be better checking this at the copy time.
 
1346
        original_inventory_keys = set()
 
1347
        inv_index = self._pack_collection.inventory_index.combined_index
 
1348
        for entry in inv_index.iter_all_entries():
 
1349
            original_inventory_keys.add(entry[1])
 
1350
        new_inventory_keys = set()
 
1351
        for entry in new_pack.inventory_index.iter_all_entries():
 
1352
            new_inventory_keys.add(entry[1])
 
1353
        if new_inventory_keys != original_inventory_keys:
 
1354
            self._data_changed = True
 
1355
        return new_pack.data_inserted() and self._data_changed
 
1356
 
 
1357
 
768
1358
class RepositoryPackCollection(object):
769
1359
    """Management of packs within a repository.
770
1360
 
771
1361
    :ivar _names: map of {pack_name: (index_size,)}
772
1362
    """
773
1363
 
774
 
    pack_factory = None
775
 
    resumed_pack_factory = None
776
 
    normal_packer_class = None
777
 
    optimising_packer_class = None
 
1364
    pack_factory = NewPack
 
1365
    resumed_pack_factory = ResumedPack
778
1366
 
779
1367
    def __init__(self, repo, transport, index_transport, upload_transport,
780
1368
                 pack_transport, index_builder_class, index_class,
815
1403
        self.inventory_index = AggregateIndex(self.reload_pack_names, flush)
816
1404
        self.text_index = AggregateIndex(self.reload_pack_names, flush)
817
1405
        self.signature_index = AggregateIndex(self.reload_pack_names, flush)
818
 
        all_indices = [self.revision_index, self.inventory_index,
819
 
                self.text_index, self.signature_index]
820
1406
        if use_chk_index:
821
1407
            self.chk_index = AggregateIndex(self.reload_pack_names, flush)
822
 
            all_indices.append(self.chk_index)
823
1408
        else:
824
1409
            # used to determine if we're using a chk_index elsewhere.
825
1410
            self.chk_index = None
826
 
        # Tell all the CombinedGraphIndex objects about each other, so they can
827
 
        # share hints about which pack names to search first.
828
 
        all_combined = [agg_idx.combined_index for agg_idx in all_indices]
829
 
        for combined_idx in all_combined:
830
 
            combined_idx.set_sibling_indices(
831
 
                set(all_combined).difference([combined_idx]))
832
1411
        # resumed packs
833
1412
        self._resumed_packs = []
834
 
        self.config_stack = config.LocationStack(self.transport.base)
835
 
 
836
 
    def __repr__(self):
837
 
        return '%s(%r)' % (self.__class__.__name__, self.repo)
838
1413
 
839
1414
    def add_pack_to_memory(self, pack):
840
1415
        """Make a Pack object available to the repository to satisfy queries.
922
1497
            'containing %d revisions. Packing %d files into %d affecting %d'
923
1498
            ' revisions', self, total_packs, total_revisions, num_old_packs,
924
1499
            num_new_packs, num_revs_affected)
925
 
        result = self._execute_pack_operations(pack_operations, packer_class=self.normal_packer_class,
 
1500
        result = self._execute_pack_operations(pack_operations,
926
1501
                                      reload_func=self._restart_autopack)
927
1502
        mutter('Auto-packing repository %s completed', self)
928
1503
        return result
929
1504
 
930
 
    def _execute_pack_operations(self, pack_operations, packer_class,
931
 
            reload_func=None):
 
1505
    def _execute_pack_operations(self, pack_operations, _packer_class=Packer,
 
1506
                                 reload_func=None):
932
1507
        """Execute a series of pack operations.
933
1508
 
934
1509
        :param pack_operations: A list of [revision_count, packs_to_combine].
935
 
        :param packer_class: The class of packer to use
 
1510
        :param _packer_class: The class of packer to use (default: Packer).
936
1511
        :return: The new pack names.
937
1512
        """
938
1513
        for revision_count, packs in pack_operations:
939
1514
            # we may have no-ops from the setup logic
940
1515
            if len(packs) == 0:
941
1516
                continue
942
 
            packer = packer_class(self, packs, '.autopack',
 
1517
            packer = _packer_class(self, packs, '.autopack',
943
1518
                                   reload_func=reload_func)
944
1519
            try:
945
 
                result = packer.pack()
 
1520
                packer.pack()
946
1521
            except errors.RetryWithNewPacks:
947
1522
                # An exception is propagating out of this context, make sure
948
1523
                # this packer has cleaned up. Packer() doesn't set its new_pack
951
1526
                if packer.new_pack is not None:
952
1527
                    packer.new_pack.abort()
953
1528
                raise
954
 
            if result is None:
955
 
                return
956
1529
            for pack in packs:
957
1530
                self._remove_pack_from_memory(pack)
958
1531
        # record the newly available packs and stop advertising the old
959
1532
        # packs
960
 
        to_be_obsoleted = []
961
 
        for _, packs in pack_operations:
962
 
            to_be_obsoleted.extend(packs)
963
 
        result = self._save_pack_names(clear_obsolete_packs=True,
964
 
                                       obsolete_packs=to_be_obsoleted)
 
1533
        result = self._save_pack_names(clear_obsolete_packs=True)
 
1534
        # Move the old packs out of the way now they are no longer referenced.
 
1535
        for revision_count, packs in pack_operations:
 
1536
            self._obsolete_packs(packs)
965
1537
        return result
966
1538
 
967
1539
    def _flush_new_pack(self):
980
1552
        """Is the collection already packed?"""
981
1553
        return not (self.repo._format.pack_compresses or (len(self._names) > 1))
982
1554
 
983
 
    def pack(self, hint=None, clean_obsolete_packs=False):
 
1555
    def pack(self, hint=None):
984
1556
        """Pack the pack collection totally."""
985
1557
        self.ensure_loaded()
986
1558
        total_packs = len(self._names)
992
1564
        mutter('Packing repository %s, which has %d pack files, '
993
1565
            'containing %d revisions with hint %r.', self, total_packs,
994
1566
            total_revisions, hint)
995
 
        while True:
996
 
            try:
997
 
                self._try_pack_operations(hint)
998
 
            except RetryPackOperations:
999
 
                continue
1000
 
            break
1001
 
 
1002
 
        if clean_obsolete_packs:
1003
 
            self._clear_obsolete_packs()
1004
 
 
1005
 
    def _try_pack_operations(self, hint):
1006
 
        """Calculate the pack operations based on the hint (if any), and
1007
 
        execute them.
1008
 
        """
1009
1567
        # determine which packs need changing
1010
1568
        pack_operations = [[0, []]]
1011
1569
        for pack in self.all_packs():
1012
 
            if hint is None or pack.name in hint:
1013
 
                # Either no hint was provided (so we are packing everything),
1014
 
                # or this pack was included in the hint.
 
1570
            if not hint or pack.name in hint:
1015
1571
                pack_operations[-1][0] += pack.get_revision_count()
1016
1572
                pack_operations[-1][1].append(pack)
1017
 
        self._execute_pack_operations(pack_operations,
1018
 
            packer_class=self.optimising_packer_class,
1019
 
            reload_func=self._restart_pack_operations)
 
1573
        self._execute_pack_operations(pack_operations, OptimisingPacker)
1020
1574
 
1021
1575
    def plan_autopack_combinations(self, existing_packs, pack_distribution):
1022
1576
        """Plan a pack operation.
1032
1586
        pack_operations = [[0, []]]
1033
1587
        # plan out what packs to keep, and what to reorganise
1034
1588
        while len(existing_packs):
1035
 
            # take the largest pack, and if it's less than the head of the
 
1589
            # take the largest pack, and if its less than the head of the
1036
1590
            # distribution chart we will include its contents in the new pack
1037
 
            # for that position. If it's larger, we remove its size from the
 
1591
            # for that position. If its larger, we remove its size from the
1038
1592
            # distribution chart
1039
1593
            next_pack_rev_count, next_pack = existing_packs.pop(0)
1040
1594
            if next_pack_rev_count >= pack_distribution[0]:
1075
1629
 
1076
1630
        :return: True if the disk names had not been previously read.
1077
1631
        """
1078
 
        # NB: if you see an assertion error here, it's probably access against
 
1632
        # NB: if you see an assertion error here, its probably access against
1079
1633
        # an unlocked repo. Naughty.
1080
1634
        if not self.repo.is_locked():
1081
1635
            raise errors.ObjectNotLocked(self.repo)
1111
1665
            txt_index = self._make_index(name, '.tix')
1112
1666
            sig_index = self._make_index(name, '.six')
1113
1667
            if self.chk_index is not None:
1114
 
                chk_index = self._make_index(name, '.cix', is_chk=True)
 
1668
                chk_index = self._make_index(name, '.cix')
1115
1669
            else:
1116
1670
                chk_index = None
1117
1671
            result = ExistingPack(self._pack_transport, name, rev_index,
1136
1690
            txt_index = self._make_index(name, '.tix', resume=True)
1137
1691
            sig_index = self._make_index(name, '.six', resume=True)
1138
1692
            if self.chk_index is not None:
1139
 
                chk_index = self._make_index(name, '.cix', resume=True,
1140
 
                                             is_chk=True)
 
1693
                chk_index = self._make_index(name, '.cix', resume=True)
1141
1694
            else:
1142
1695
                chk_index = None
1143
1696
            result = self.resumed_pack_factory(name, rev_index, inv_index,
1173
1726
        return self._index_class(self.transport, 'pack-names', None
1174
1727
                ).iter_all_entries()
1175
1728
 
1176
 
    def _make_index(self, name, suffix, resume=False, is_chk=False):
 
1729
    def _make_index(self, name, suffix, resume=False):
1177
1730
        size_offset = self._suffix_offsets[suffix]
1178
1731
        index_name = name + suffix
1179
1732
        if resume:
1182
1735
        else:
1183
1736
            transport = self._index_transport
1184
1737
            index_size = self._names[name][size_offset]
1185
 
        index = self._index_class(transport, index_name, index_size,
1186
 
                                  unlimited_cache=is_chk)
1187
 
        if is_chk and self._index_class is btree_index.BTreeGraphIndex: 
1188
 
            index._leaf_factory = btree_index._gcchk_factory
1189
 
        return index
 
1738
        return self._index_class(transport, index_name, index_size)
1190
1739
 
1191
1740
    def _max_pack_count(self, total_revisions):
1192
1741
        """Return the maximum number of packs to use for total revisions.
1220
1769
        :param return: None.
1221
1770
        """
1222
1771
        for pack in packs:
1223
 
            try:
1224
 
                pack.pack_transport.move(pack.file_name(),
1225
 
                    '../obsolete_packs/' + pack.file_name())
1226
 
            except (errors.PathError, errors.TransportError), e:
1227
 
                # TODO: Should these be warnings or mutters?
1228
 
                mutter("couldn't rename obsolete pack, skipping it:\n%s"
1229
 
                       % (e,))
 
1772
            pack.pack_transport.rename(pack.file_name(),
 
1773
                '../obsolete_packs/' + pack.file_name())
1230
1774
            # TODO: Probably needs to know all possible indices for this pack
1231
1775
            # - or maybe list the directory and move all indices matching this
1232
1776
            # name whether we recognize it or not?
1234
1778
            if self.chk_index is not None:
1235
1779
                suffixes.append('.cix')
1236
1780
            for suffix in suffixes:
1237
 
                try:
1238
 
                    self._index_transport.move(pack.name + suffix,
1239
 
                        '../obsolete_packs/' + pack.name + suffix)
1240
 
                except (errors.PathError, errors.TransportError), e:
1241
 
                    mutter("couldn't rename obsolete index, skipping it:\n%s"
1242
 
                           % (e,))
 
1781
                self._index_transport.rename(pack.name + suffix,
 
1782
                    '../obsolete_packs/' + pack.name + suffix)
1243
1783
 
1244
1784
    def pack_distribution(self, total_revisions):
1245
1785
        """Generate a list of the number of revisions to put in each pack.
1271
1811
        self._remove_pack_indices(pack)
1272
1812
        self.packs.remove(pack)
1273
1813
 
1274
 
    def _remove_pack_indices(self, pack, ignore_missing=False):
1275
 
        """Remove the indices for pack from the aggregated indices.
1276
 
        
1277
 
        :param ignore_missing: Suppress KeyErrors from calling remove_index.
1278
 
        """
1279
 
        for index_type in Pack.index_definitions.keys():
1280
 
            attr_name = index_type + '_index'
1281
 
            aggregate_index = getattr(self, attr_name)
1282
 
            if aggregate_index is not None:
1283
 
                pack_index = getattr(pack, attr_name)
1284
 
                try:
1285
 
                    aggregate_index.remove_index(pack_index)
1286
 
                except KeyError:
1287
 
                    if ignore_missing:
1288
 
                        continue
1289
 
                    raise
 
1814
    def _remove_pack_indices(self, pack):
 
1815
        """Remove the indices for pack from the aggregated indices."""
 
1816
        self.revision_index.remove_index(pack.revision_index, pack)
 
1817
        self.inventory_index.remove_index(pack.inventory_index, pack)
 
1818
        self.text_index.remove_index(pack.text_index, pack)
 
1819
        self.signature_index.remove_index(pack.signature_index, pack)
 
1820
        if self.chk_index is not None:
 
1821
            self.chk_index.remove_index(pack.chk_index, pack)
1290
1822
 
1291
1823
    def reset(self):
1292
1824
        """Clear all cached data."""
1325
1857
        disk_nodes = set()
1326
1858
        for index, key, value in self._iter_disk_pack_index():
1327
1859
            disk_nodes.add((key, value))
1328
 
        orig_disk_nodes = set(disk_nodes)
1329
1860
 
1330
1861
        # do a two-way diff against our original content
1331
1862
        current_nodes = set()
1344
1875
        disk_nodes.difference_update(deleted_nodes)
1345
1876
        disk_nodes.update(new_nodes)
1346
1877
 
1347
 
        return disk_nodes, deleted_nodes, new_nodes, orig_disk_nodes
 
1878
        return disk_nodes, deleted_nodes, new_nodes
1348
1879
 
1349
1880
    def _syncronize_pack_names_from_disk_nodes(self, disk_nodes):
1350
1881
        """Given the correct set of pack files, update our saved info.
1378
1909
                    # disk index because the set values are the same, unless
1379
1910
                    # the only index shows up as deleted by the set difference
1380
1911
                    # - which it may. Until there is a specific test for this,
1381
 
                    # assume it's broken. RBC 20071017.
 
1912
                    # assume its broken. RBC 20071017.
1382
1913
                    self._remove_pack_from_memory(self.get_pack_by_name(name))
1383
1914
                    self._names[name] = sizes
1384
1915
                    self.get_pack_by_name(name)
1390
1921
                added.append(name)
1391
1922
        return removed, added, modified
1392
1923
 
1393
 
    def _save_pack_names(self, clear_obsolete_packs=False, obsolete_packs=None):
 
1924
    def _save_pack_names(self, clear_obsolete_packs=False):
1394
1925
        """Save the list of packs.
1395
1926
 
1396
1927
        This will take out the mutex around the pack names list for the
1400
1931
 
1401
1932
        :param clear_obsolete_packs: If True, clear out the contents of the
1402
1933
            obsolete_packs directory.
1403
 
        :param obsolete_packs: Packs that are obsolete once the new pack-names
1404
 
            file has been written.
1405
1934
        :return: A list of the names saved that were not previously on disk.
1406
1935
        """
1407
 
        already_obsolete = []
1408
1936
        self.lock_names()
1409
1937
        try:
1410
1938
            builder = self._index_builder_class()
1411
 
            (disk_nodes, deleted_nodes, new_nodes,
1412
 
             orig_disk_nodes) = self._diff_pack_names()
 
1939
            disk_nodes, deleted_nodes, new_nodes = self._diff_pack_names()
1413
1940
            # TODO: handle same-name, index-size-changes here -
1414
1941
            # e.g. use the value from disk, not ours, *unless* we're the one
1415
1942
            # changing it.
1417
1944
                builder.add_node(key, value)
1418
1945
            self.transport.put_file('pack-names', builder.finish(),
1419
1946
                mode=self.repo.bzrdir._get_file_mode())
 
1947
            # move the baseline forward
1420
1948
            self._packs_at_load = disk_nodes
1421
1949
            if clear_obsolete_packs:
1422
 
                to_preserve = None
1423
 
                if obsolete_packs:
1424
 
                    to_preserve = set([o.name for o in obsolete_packs])
1425
 
                already_obsolete = self._clear_obsolete_packs(to_preserve)
 
1950
                self._clear_obsolete_packs()
1426
1951
        finally:
1427
1952
            self._unlock_names()
1428
1953
        # synchronise the memory packs list with what we just wrote:
1429
1954
        self._syncronize_pack_names_from_disk_nodes(disk_nodes)
1430
 
        if obsolete_packs:
1431
 
            # TODO: We could add one more condition here. "if o.name not in
1432
 
            #       orig_disk_nodes and o != the new_pack we haven't written to
1433
 
            #       disk yet. However, the new pack object is not easily
1434
 
            #       accessible here (it would have to be passed through the
1435
 
            #       autopacking code, etc.)
1436
 
            obsolete_packs = [o for o in obsolete_packs
1437
 
                              if o.name not in already_obsolete]
1438
 
            self._obsolete_packs(obsolete_packs)
1439
1955
        return [new_node[0][0] for new_node in new_nodes]
1440
1956
 
1441
1957
    def reload_pack_names(self):
1449
1965
        """
1450
1966
        # The ensure_loaded call is to handle the case where the first call
1451
1967
        # made involving the collection was to reload_pack_names, where we 
1452
 
        # don't have a view of disk contents. It's a bit of a bandaid, and
1453
 
        # causes two reads of pack-names, but it's a rare corner case not
1454
 
        # struck with regular push/pull etc.
 
1968
        # don't have a view of disk contents. Its a bit of a bandaid, and
 
1969
        # causes two reads of pack-names, but its a rare corner case not struck
 
1970
        # with regular push/pull etc.
1455
1971
        first_read = self.ensure_loaded()
1456
1972
        if first_read:
1457
1973
            return True
1458
1974
        # out the new value.
1459
 
        (disk_nodes, deleted_nodes, new_nodes,
1460
 
         orig_disk_nodes) = self._diff_pack_names()
1461
 
        # _packs_at_load is meant to be the explicit list of names in
1462
 
        # 'pack-names' at then start. As such, it should not contain any
1463
 
        # pending names that haven't been written out yet.
1464
 
        self._packs_at_load = orig_disk_nodes
 
1975
        disk_nodes, _, _ = self._diff_pack_names()
 
1976
        self._packs_at_load = disk_nodes
1465
1977
        (removed, added,
1466
1978
         modified) = self._syncronize_pack_names_from_disk_nodes(disk_nodes)
1467
1979
        if removed or added or modified:
1476
1988
            raise
1477
1989
        raise errors.RetryAutopack(self.repo, False, sys.exc_info())
1478
1990
 
1479
 
    def _restart_pack_operations(self):
1480
 
        """Reload the pack names list, and restart the autopack code."""
1481
 
        if not self.reload_pack_names():
1482
 
            # Re-raise the original exception, because something went missing
1483
 
            # and a restart didn't find it
1484
 
            raise
1485
 
        raise RetryPackOperations(self.repo, False, sys.exc_info())
1486
 
 
1487
 
    def _clear_obsolete_packs(self, preserve=None):
 
1991
    def _clear_obsolete_packs(self):
1488
1992
        """Delete everything from the obsolete-packs directory.
1489
 
 
1490
 
        :return: A list of pack identifiers (the filename without '.pack') that
1491
 
            were found in obsolete_packs.
1492
1993
        """
1493
 
        found = []
1494
1994
        obsolete_pack_transport = self.transport.clone('obsolete_packs')
1495
 
        if preserve is None:
1496
 
            preserve = set()
1497
1995
        for filename in obsolete_pack_transport.list_dir('.'):
1498
 
            name, ext = osutils.splitext(filename)
1499
 
            if ext == '.pack':
1500
 
                found.append(name)
1501
 
            if name in preserve:
1502
 
                continue
1503
1996
            try:
1504
1997
                obsolete_pack_transport.delete(filename)
1505
1998
            except (errors.PathError, errors.TransportError), e:
1506
 
                warning("couldn't delete obsolete pack, skipping it:\n%s"
1507
 
                        % (e,))
1508
 
        return found
 
1999
                warning("couldn't delete obsolete pack, skipping it:\n%s" % (e,))
1509
2000
 
1510
2001
    def _start_write_group(self):
1511
2002
        # Do not permit preparation for writing if we're not in a 'write lock'.
1538
2029
        # FIXME: just drop the transient index.
1539
2030
        # forget what names there are
1540
2031
        if self._new_pack is not None:
1541
 
            operation = cleanup.OperationWithCleanups(self._new_pack.abort)
1542
 
            operation.add_cleanup(setattr, self, '_new_pack', None)
1543
 
            # If we aborted while in the middle of finishing the write
1544
 
            # group, _remove_pack_indices could fail because the indexes are
1545
 
            # already gone.  But they're not there we shouldn't fail in this
1546
 
            # case, so we pass ignore_missing=True.
1547
 
            operation.add_cleanup(self._remove_pack_indices, self._new_pack,
1548
 
                ignore_missing=True)
1549
 
            operation.run_simple()
 
2032
            try:
 
2033
                self._new_pack.abort()
 
2034
            finally:
 
2035
                # XXX: If we aborted while in the middle of finishing the write
 
2036
                # group, _remove_pack_indices can fail because the indexes are
 
2037
                # already gone.  If they're not there we shouldn't fail in this
 
2038
                # case.  -- mbp 20081113
 
2039
                self._remove_pack_indices(self._new_pack)
 
2040
                self._new_pack = None
1550
2041
        for resumed_pack in self._resumed_packs:
1551
 
            operation = cleanup.OperationWithCleanups(resumed_pack.abort)
1552
 
            # See comment in previous finally block.
1553
 
            operation.add_cleanup(self._remove_pack_indices, resumed_pack,
1554
 
                ignore_missing=True)
1555
 
            operation.run_simple()
 
2042
            try:
 
2043
                resumed_pack.abort()
 
2044
            finally:
 
2045
                # See comment in previous finally block.
 
2046
                try:
 
2047
                    self._remove_pack_indices(resumed_pack)
 
2048
                except KeyError:
 
2049
                    pass
1556
2050
        del self._resumed_packs[:]
1557
2051
 
1558
2052
    def _remove_resumed_pack_indices(self):
1560
2054
            self._remove_pack_indices(resumed_pack)
1561
2055
        del self._resumed_packs[:]
1562
2056
 
1563
 
    def _check_new_inventories(self):
1564
 
        """Detect missing inventories in this write group.
1565
 
 
1566
 
        :returns: list of strs, summarising any problems found.  If the list is
1567
 
            empty no problems were found.
1568
 
        """
1569
 
        # The base implementation does no checks.  GCRepositoryPackCollection
1570
 
        # overrides this.
1571
 
        return []
1572
 
        
1573
2057
    def _commit_write_group(self):
1574
2058
        all_missing = set()
1575
2059
        for prefix, versioned_file in (
1584
2068
            raise errors.BzrCheckError(
1585
2069
                "Repository %s has missing compression parent(s) %r "
1586
2070
                 % (self.repo, sorted(all_missing)))
1587
 
        problems = self._check_new_inventories()
1588
 
        if problems:
1589
 
            problems_summary = '\n'.join(problems)
1590
 
            raise errors.BzrCheckError(
1591
 
                "Cannot add revision(s) to repository: " + problems_summary)
1592
2071
        self._remove_pack_indices(self._new_pack)
1593
 
        any_new_content = False
 
2072
        should_autopack = False
1594
2073
        if self._new_pack.data_inserted():
1595
2074
            # get all the data to disk and read to use
1596
2075
            self._new_pack.finish()
1597
2076
            self.allocate(self._new_pack)
1598
2077
            self._new_pack = None
1599
 
            any_new_content = True
 
2078
            should_autopack = True
1600
2079
        else:
1601
2080
            self._new_pack.abort()
1602
2081
            self._new_pack = None
1607
2086
            self._remove_pack_from_memory(resumed_pack)
1608
2087
            resumed_pack.finish()
1609
2088
            self.allocate(resumed_pack)
1610
 
            any_new_content = True
 
2089
            should_autopack = True
1611
2090
        del self._resumed_packs[:]
1612
 
        if any_new_content:
1613
 
            result = self.autopack()
1614
 
            if not result:
 
2091
        if should_autopack:
 
2092
            if not self.autopack():
1615
2093
                # when autopack takes no steps, the names list is still
1616
2094
                # unsaved.
1617
2095
                return self._save_pack_names()
1618
 
            return result
1619
 
        return []
1620
2096
 
1621
2097
    def _suspend_write_group(self):
1622
2098
        tokens = [pack.name for pack in self._resumed_packs]
1637
2113
            self._resume_pack(token)
1638
2114
 
1639
2115
 
1640
 
class PackRepository(MetaDirVersionedFileRepository):
 
2116
class KnitPackRepository(KnitRepository):
1641
2117
    """Repository with knit objects stored inside pack containers.
1642
2118
 
1643
2119
    The layering for a KnitPackRepository is:
1646
2122
    ===================================================
1647
2123
    Tuple based apis below, string based, and key based apis above
1648
2124
    ---------------------------------------------------
1649
 
    VersionedFiles
 
2125
    KnitVersionedFiles
1650
2126
      Provides .texts, .revisions etc
1651
2127
      This adapts the N-tuple keys to physical knit records which only have a
1652
2128
      single string identifier (for historical reasons), which in older formats
1662
2138
 
1663
2139
    """
1664
2140
 
1665
 
    # These attributes are inherited from the Repository base class. Setting
1666
 
    # them to None ensures that if the constructor is changed to not initialize
1667
 
    # them, or a subclass fails to call the constructor, that an error will
1668
 
    # occur rather than the system working but generating incorrect data.
1669
 
    _commit_builder_class = None
1670
 
    _serializer = None
1671
 
 
1672
2141
    def __init__(self, _format, a_bzrdir, control_files, _commit_builder_class,
1673
2142
        _serializer):
1674
 
        MetaDirRepository.__init__(self, _format, a_bzrdir, control_files)
1675
 
        self._commit_builder_class = _commit_builder_class
1676
 
        self._serializer = _serializer
 
2143
        KnitRepository.__init__(self, _format, a_bzrdir, control_files,
 
2144
            _commit_builder_class, _serializer)
 
2145
        index_transport = self._transport.clone('indices')
 
2146
        self._pack_collection = RepositoryPackCollection(self, self._transport,
 
2147
            index_transport,
 
2148
            self._transport.clone('upload'),
 
2149
            self._transport.clone('packs'),
 
2150
            _format.index_builder_class,
 
2151
            _format.index_class,
 
2152
            use_chk_index=self._format.supports_chks,
 
2153
            )
 
2154
        self.inventories = KnitVersionedFiles(
 
2155
            _KnitGraphIndex(self._pack_collection.inventory_index.combined_index,
 
2156
                add_callback=self._pack_collection.inventory_index.add_callback,
 
2157
                deltas=True, parents=True, is_locked=self.is_locked),
 
2158
            data_access=self._pack_collection.inventory_index.data_access,
 
2159
            max_delta_chain=200)
 
2160
        self.revisions = KnitVersionedFiles(
 
2161
            _KnitGraphIndex(self._pack_collection.revision_index.combined_index,
 
2162
                add_callback=self._pack_collection.revision_index.add_callback,
 
2163
                deltas=False, parents=True, is_locked=self.is_locked,
 
2164
                track_external_parent_refs=True),
 
2165
            data_access=self._pack_collection.revision_index.data_access,
 
2166
            max_delta_chain=0)
 
2167
        self.signatures = KnitVersionedFiles(
 
2168
            _KnitGraphIndex(self._pack_collection.signature_index.combined_index,
 
2169
                add_callback=self._pack_collection.signature_index.add_callback,
 
2170
                deltas=False, parents=False, is_locked=self.is_locked),
 
2171
            data_access=self._pack_collection.signature_index.data_access,
 
2172
            max_delta_chain=0)
 
2173
        self.texts = KnitVersionedFiles(
 
2174
            _KnitGraphIndex(self._pack_collection.text_index.combined_index,
 
2175
                add_callback=self._pack_collection.text_index.add_callback,
 
2176
                deltas=True, parents=True, is_locked=self.is_locked),
 
2177
            data_access=self._pack_collection.text_index.data_access,
 
2178
            max_delta_chain=200)
 
2179
        if _format.supports_chks:
 
2180
            # No graph, no compression:- references from chks are between
 
2181
            # different objects not temporal versions of the same; and without
 
2182
            # some sort of temporal structure knit compression will just fail.
 
2183
            self.chk_bytes = KnitVersionedFiles(
 
2184
                _KnitGraphIndex(self._pack_collection.chk_index.combined_index,
 
2185
                    add_callback=self._pack_collection.chk_index.add_callback,
 
2186
                    deltas=False, parents=False, is_locked=self.is_locked),
 
2187
                data_access=self._pack_collection.chk_index.data_access,
 
2188
                max_delta_chain=0)
 
2189
        else:
 
2190
            self.chk_bytes = None
 
2191
        # True when the repository object is 'write locked' (as opposed to the
 
2192
        # physical lock only taken out around changes to the pack-names list.)
 
2193
        # Another way to represent this would be a decorator around the control
 
2194
        # files object that presents logical locks as physical ones - if this
 
2195
        # gets ugly consider that alternative design. RBC 20071011
 
2196
        self._write_lock_count = 0
 
2197
        self._transaction = None
 
2198
        # for tests
 
2199
        self._reconcile_does_inventory_gc = True
1677
2200
        self._reconcile_fixes_text_parents = True
1678
 
        if self._format.supports_external_lookups:
1679
 
            self._unstacked_provider = graph.CachingParentsProvider(
1680
 
                self._make_parents_provider_unstacked())
1681
 
        else:
1682
 
            self._unstacked_provider = graph.CachingParentsProvider(self)
1683
 
        self._unstacked_provider.disable_cache()
 
2201
        self._reconcile_backsup_inventory = False
1684
2202
 
1685
 
    @needs_read_lock
1686
 
    def _all_revision_ids(self):
1687
 
        """See Repository.all_revision_ids()."""
1688
 
        return [key[0] for key in self.revisions.keys()]
 
2203
    def _warn_if_deprecated(self):
 
2204
        # This class isn't deprecated, but one sub-format is
 
2205
        if isinstance(self._format, RepositoryFormatKnitPack5RichRootBroken):
 
2206
            from bzrlib import repository
 
2207
            if repository._deprecation_warning_done:
 
2208
                return
 
2209
            repository._deprecation_warning_done = True
 
2210
            warning("Format %s for %s is deprecated - please use"
 
2211
                    " 'bzr upgrade --1.6.1-rich-root'"
 
2212
                    % (self._format, self.bzrdir.transport.base))
1689
2213
 
1690
2214
    def _abort_write_group(self):
1691
 
        self.revisions._index._key_dependencies.clear()
 
2215
        self.revisions._index._key_dependencies.refs.clear()
1692
2216
        self._pack_collection._abort_write_group()
1693
2217
 
 
2218
    def _find_inconsistent_revision_parents(self):
 
2219
        """Find revisions with incorrectly cached parents.
 
2220
 
 
2221
        :returns: an iterator yielding tuples of (revison-id, parents-in-index,
 
2222
            parents-in-revision).
 
2223
        """
 
2224
        if not self.is_locked():
 
2225
            raise errors.ObjectNotLocked(self)
 
2226
        pb = ui.ui_factory.nested_progress_bar()
 
2227
        result = []
 
2228
        try:
 
2229
            revision_nodes = self._pack_collection.revision_index \
 
2230
                .combined_index.iter_all_entries()
 
2231
            index_positions = []
 
2232
            # Get the cached index values for all revisions, and also the
 
2233
            # location in each index of the revision text so we can perform
 
2234
            # linear IO.
 
2235
            for index, key, value, refs in revision_nodes:
 
2236
                node = (index, key, value, refs)
 
2237
                index_memo = self.revisions._index._node_to_position(node)
 
2238
                if index_memo[0] != index:
 
2239
                    raise AssertionError('%r != %r' % (index_memo[0], index))
 
2240
                index_positions.append((index_memo, key[0],
 
2241
                                       tuple(parent[0] for parent in refs[0])))
 
2242
                pb.update("Reading revision index", 0, 0)
 
2243
            index_positions.sort()
 
2244
            batch_size = 1000
 
2245
            pb.update("Checking cached revision graph", 0,
 
2246
                      len(index_positions))
 
2247
            for offset in xrange(0, len(index_positions), 1000):
 
2248
                pb.update("Checking cached revision graph", offset)
 
2249
                to_query = index_positions[offset:offset + batch_size]
 
2250
                if not to_query:
 
2251
                    break
 
2252
                rev_ids = [item[1] for item in to_query]
 
2253
                revs = self.get_revisions(rev_ids)
 
2254
                for revision, item in zip(revs, to_query):
 
2255
                    index_parents = item[2]
 
2256
                    rev_parents = tuple(revision.parent_ids)
 
2257
                    if index_parents != rev_parents:
 
2258
                        result.append((revision.revision_id, index_parents,
 
2259
                                       rev_parents))
 
2260
        finally:
 
2261
            pb.finished()
 
2262
        return result
 
2263
 
 
2264
    def _get_source(self, to_format):
 
2265
        if to_format.network_name() == self._format.network_name():
 
2266
            return KnitPackStreamSource(self, to_format)
 
2267
        return super(KnitPackRepository, self)._get_source(to_format)
 
2268
 
1694
2269
    def _make_parents_provider(self):
1695
 
        if not self._format.supports_external_lookups:
1696
 
            return self._unstacked_provider
1697
 
        return graph.StackedParentsProvider(_LazyListJoin(
1698
 
            [self._unstacked_provider], self._fallback_repositories))
 
2270
        return graph.CachingParentsProvider(self)
1699
2271
 
1700
2272
    def _refresh_data(self):
1701
2273
        if not self.is_locked():
1702
2274
            return
1703
2275
        self._pack_collection.reload_pack_names()
1704
 
        self._unstacked_provider.disable_cache()
1705
 
        self._unstacked_provider.enable_cache()
1706
2276
 
1707
2277
    def _start_write_group(self):
1708
2278
        self._pack_collection._start_write_group()
1709
2279
 
1710
2280
    def _commit_write_group(self):
1711
 
        hint = self._pack_collection._commit_write_group()
1712
 
        self.revisions._index._key_dependencies.clear()
1713
 
        # The commit may have added keys that were previously cached as
1714
 
        # missing, so reset the cache.
1715
 
        self._unstacked_provider.disable_cache()
1716
 
        self._unstacked_provider.enable_cache()
1717
 
        return hint
 
2281
        self.revisions._index._key_dependencies.refs.clear()
 
2282
        return self._pack_collection._commit_write_group()
1718
2283
 
1719
2284
    def suspend_write_group(self):
1720
2285
        # XXX check self._write_group is self.get_transaction()?
1721
2286
        tokens = self._pack_collection._suspend_write_group()
1722
 
        self.revisions._index._key_dependencies.clear()
 
2287
        self.revisions._index._key_dependencies.refs.clear()
1723
2288
        self._write_group = None
1724
2289
        return tokens
1725
2290
 
1746
2311
        return self._write_lock_count
1747
2312
 
1748
2313
    def lock_write(self, token=None):
1749
 
        """Lock the repository for writes.
1750
 
 
1751
 
        :return: A bzrlib.repository.RepositoryWriteLockResult.
1752
 
        """
1753
2314
        locked = self.is_locked()
1754
2315
        if not self._write_lock_count and locked:
1755
2316
            raise errors.ReadOnlyError(self)
1757
2318
        if self._write_lock_count == 1:
1758
2319
            self._transaction = transactions.WriteTransaction()
1759
2320
        if not locked:
1760
 
            if 'relock' in debug.debug_flags and self._prev_lock == 'w':
1761
 
                note('%r was write locked again', self)
1762
 
            self._prev_lock = 'w'
1763
 
            self._unstacked_provider.enable_cache()
1764
2321
            for repo in self._fallback_repositories:
1765
2322
                # Writes don't affect fallback repos
1766
2323
                repo.lock_read()
1767
2324
            self._refresh_data()
1768
 
        return RepositoryWriteLockResult(self.unlock, None)
1769
2325
 
1770
2326
    def lock_read(self):
1771
 
        """Lock the repository for reads.
1772
 
 
1773
 
        :return: A bzrlib.lock.LogicalLockResult.
1774
 
        """
1775
2327
        locked = self.is_locked()
1776
2328
        if self._write_lock_count:
1777
2329
            self._write_lock_count += 1
1778
2330
        else:
1779
2331
            self.control_files.lock_read()
1780
2332
        if not locked:
1781
 
            if 'relock' in debug.debug_flags and self._prev_lock == 'r':
1782
 
                note('%r was read locked again', self)
1783
 
            self._prev_lock = 'r'
1784
 
            self._unstacked_provider.enable_cache()
1785
2333
            for repo in self._fallback_repositories:
1786
2334
                repo.lock_read()
1787
2335
            self._refresh_data()
1788
 
        return LogicalLockResult(self.unlock)
1789
2336
 
1790
2337
    def leave_lock_in_place(self):
1791
2338
        # not supported - raise an error
1796
2343
        raise NotImplementedError(self.dont_leave_lock_in_place)
1797
2344
 
1798
2345
    @needs_write_lock
1799
 
    def pack(self, hint=None, clean_obsolete_packs=False):
 
2346
    def pack(self, hint=None):
1800
2347
        """Compress the data within the repository.
1801
2348
 
1802
2349
        This will pack all the data to a single pack. In future it may
1803
2350
        recompress deltas or do other such expensive operations.
1804
2351
        """
1805
 
        self._pack_collection.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
 
2352
        self._pack_collection.pack(hint=hint)
1806
2353
 
1807
2354
    @needs_write_lock
1808
2355
    def reconcile(self, other=None, thorough=False):
1813
2360
        return reconciler
1814
2361
 
1815
2362
    def _reconcile_pack(self, collection, packs, extension, revs, pb):
1816
 
        raise NotImplementedError(self._reconcile_pack)
 
2363
        packer = ReconcilePacker(collection, packs, extension, revs)
 
2364
        return packer.pack(pb)
1817
2365
 
1818
 
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1819
2366
    def unlock(self):
1820
2367
        if self._write_lock_count == 1 and self._write_group is not None:
1821
2368
            self.abort_write_group()
1822
 
            self._unstacked_provider.disable_cache()
1823
2369
            self._transaction = None
1824
2370
            self._write_lock_count = 0
1825
2371
            raise errors.BzrError(
1835
2381
            self.control_files.unlock()
1836
2382
 
1837
2383
        if not self.is_locked():
1838
 
            self._unstacked_provider.disable_cache()
1839
2384
            for repo in self._fallback_repositories:
1840
2385
                repo.unlock()
1841
2386
 
1842
2387
 
1843
 
class RepositoryFormatPack(MetaDirVersionedFileRepositoryFormat):
 
2388
class KnitPackStreamSource(StreamSource):
 
2389
    """A StreamSource used to transfer data between same-format KnitPack repos.
 
2390
 
 
2391
    This source assumes:
 
2392
        1) Same serialization format for all objects
 
2393
        2) Same root information
 
2394
        3) XML format inventories
 
2395
        4) Atomic inserts (so we can stream inventory texts before text
 
2396
           content)
 
2397
        5) No chk_bytes
 
2398
    """
 
2399
 
 
2400
    def __init__(self, from_repository, to_format):
 
2401
        super(KnitPackStreamSource, self).__init__(from_repository, to_format)
 
2402
        self._text_keys = None
 
2403
        self._text_fetch_order = 'unordered'
 
2404
 
 
2405
    def _get_filtered_inv_stream(self, revision_ids):
 
2406
        from_repo = self.from_repository
 
2407
        parent_ids = from_repo._find_parent_ids_of_revisions(revision_ids)
 
2408
        parent_keys = [(p,) for p in parent_ids]
 
2409
        find_text_keys = from_repo._find_text_key_references_from_xml_inventory_lines
 
2410
        parent_text_keys = set(find_text_keys(
 
2411
            from_repo._inventory_xml_lines_for_keys(parent_keys)))
 
2412
        content_text_keys = set()
 
2413
        knit = KnitVersionedFiles(None, None)
 
2414
        factory = KnitPlainFactory()
 
2415
        def find_text_keys_from_content(record):
 
2416
            if record.storage_kind not in ('knit-delta-gz', 'knit-ft-gz'):
 
2417
                raise ValueError("Unknown content storage kind for"
 
2418
                    " inventory text: %s" % (record.storage_kind,))
 
2419
            # It's a knit record, it has a _raw_record field (even if it was
 
2420
            # reconstituted from a network stream).
 
2421
            raw_data = record._raw_record
 
2422
            # read the entire thing
 
2423
            revision_id = record.key[-1]
 
2424
            content, _ = knit._parse_record(revision_id, raw_data)
 
2425
            if record.storage_kind == 'knit-delta-gz':
 
2426
                line_iterator = factory.get_linedelta_content(content)
 
2427
            elif record.storage_kind == 'knit-ft-gz':
 
2428
                line_iterator = factory.get_fulltext_content(content)
 
2429
            content_text_keys.update(find_text_keys(
 
2430
                [(line, revision_id) for line in line_iterator]))
 
2431
        revision_keys = [(r,) for r in revision_ids]
 
2432
        def _filtered_inv_stream():
 
2433
            source_vf = from_repo.inventories
 
2434
            stream = source_vf.get_record_stream(revision_keys,
 
2435
                                                 'unordered', False)
 
2436
            for record in stream:
 
2437
                if record.storage_kind == 'absent':
 
2438
                    raise errors.NoSuchRevision(from_repo, record.key)
 
2439
                find_text_keys_from_content(record)
 
2440
                yield record
 
2441
            self._text_keys = content_text_keys - parent_text_keys
 
2442
        return ('inventories', _filtered_inv_stream())
 
2443
 
 
2444
    def _get_text_stream(self):
 
2445
        # Note: We know we don't have to handle adding root keys, because both
 
2446
        # the source and target are the identical network name.
 
2447
        text_stream = self.from_repository.texts.get_record_stream(
 
2448
                        self._text_keys, self._text_fetch_order, False)
 
2449
        return ('texts', text_stream)
 
2450
 
 
2451
    def get_stream(self, search):
 
2452
        revision_ids = search.get_keys()
 
2453
        for stream_info in self._fetch_revision_texts(revision_ids):
 
2454
            yield stream_info
 
2455
        self._revision_keys = [(rev_id,) for rev_id in revision_ids]
 
2456
        yield self._get_filtered_inv_stream(revision_ids)
 
2457
        yield self._get_text_stream()
 
2458
 
 
2459
 
 
2460
 
 
2461
class RepositoryFormatPack(MetaDirRepositoryFormat):
1844
2462
    """Format logic for pack structured repositories.
1845
2463
 
1846
2464
    This repository format has:
1876
2494
    index_class = None
1877
2495
    _fetch_uses_deltas = True
1878
2496
    fast_deltas = False
1879
 
    supports_funky_characters = True
1880
 
    revision_graph_can_have_wrong_parents = True
1881
2497
 
1882
2498
    def initialize(self, a_bzrdir, shared=False):
1883
2499
        """Create a pack based repository.
1894
2510
        utf8_files = [('format', self.get_format_string())]
1895
2511
 
1896
2512
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1897
 
        repository = self.open(a_bzrdir=a_bzrdir, _found=True)
1898
 
        self._run_post_repo_init_hooks(repository, a_bzrdir, shared)
1899
 
        return repository
 
2513
        return self.open(a_bzrdir=a_bzrdir, _found=True)
1900
2514
 
1901
2515
    def open(self, a_bzrdir, _found=False, _override_transport=None):
1902
2516
        """See RepositoryFormat.open().
1920
2534
                              _serializer=self._serializer)
1921
2535
 
1922
2536
 
1923
 
class RetryPackOperations(errors.RetryWithNewPacks):
1924
 
    """Raised when we are packing and we find a missing file.
1925
 
 
1926
 
    Meant as a signaling exception, to tell the RepositoryPackCollection.pack
1927
 
    code it should try again.
1928
 
    """
1929
 
 
1930
 
    internal_error = True
1931
 
 
1932
 
    _fmt = ("Pack files have changed, reload and try pack again."
1933
 
            " context: %(context)s %(orig_error)s")
1934
 
 
1935
 
 
1936
 
class _DirectPackAccess(object):
1937
 
    """Access to data in one or more packs with less translation."""
1938
 
 
1939
 
    def __init__(self, index_to_packs, reload_func=None, flush_func=None):
1940
 
        """Create a _DirectPackAccess object.
1941
 
 
1942
 
        :param index_to_packs: A dict mapping index objects to the transport
1943
 
            and file names for obtaining data.
1944
 
        :param reload_func: A function to call if we determine that the pack
1945
 
            files have moved and we need to reload our caches. See
1946
 
            bzrlib.repo_fmt.pack_repo.AggregateIndex for more details.
1947
 
        """
1948
 
        self._container_writer = None
1949
 
        self._write_index = None
1950
 
        self._indices = index_to_packs
1951
 
        self._reload_func = reload_func
1952
 
        self._flush_func = flush_func
1953
 
 
1954
 
    def add_raw_records(self, key_sizes, raw_data):
1955
 
        """Add raw knit bytes to a storage area.
1956
 
 
1957
 
        The data is spooled to the container writer in one bytes-record per
1958
 
        raw data item.
1959
 
 
1960
 
        :param sizes: An iterable of tuples containing the key and size of each
1961
 
            raw data segment.
1962
 
        :param raw_data: A bytestring containing the data.
1963
 
        :return: A list of memos to retrieve the record later. Each memo is an
1964
 
            opaque index memo. For _DirectPackAccess the memo is (index, pos,
1965
 
            length), where the index field is the write_index object supplied
1966
 
            to the PackAccess object.
1967
 
        """
1968
 
        if type(raw_data) is not str:
1969
 
            raise AssertionError(
1970
 
                'data must be plain bytes was %s' % type(raw_data))
1971
 
        result = []
1972
 
        offset = 0
1973
 
        for key, size in key_sizes:
1974
 
            p_offset, p_length = self._container_writer.add_bytes_record(
1975
 
                raw_data[offset:offset+size], [])
1976
 
            offset += size
1977
 
            result.append((self._write_index, p_offset, p_length))
1978
 
        return result
1979
 
 
1980
 
    def flush(self):
1981
 
        """Flush pending writes on this access object.
1982
 
 
1983
 
        This will flush any buffered writes to a NewPack.
1984
 
        """
1985
 
        if self._flush_func is not None:
1986
 
            self._flush_func()
1987
 
 
1988
 
    def get_raw_records(self, memos_for_retrieval):
1989
 
        """Get the raw bytes for a records.
1990
 
 
1991
 
        :param memos_for_retrieval: An iterable containing the (index, pos,
1992
 
            length) memo for retrieving the bytes. The Pack access method
1993
 
            looks up the pack to use for a given record in its index_to_pack
1994
 
            map.
1995
 
        :return: An iterator over the bytes of the records.
1996
 
        """
1997
 
        # first pass, group into same-index requests
1998
 
        request_lists = []
1999
 
        current_index = None
2000
 
        for (index, offset, length) in memos_for_retrieval:
2001
 
            if current_index == index:
2002
 
                current_list.append((offset, length))
2003
 
            else:
2004
 
                if current_index is not None:
2005
 
                    request_lists.append((current_index, current_list))
2006
 
                current_index = index
2007
 
                current_list = [(offset, length)]
2008
 
        # handle the last entry
2009
 
        if current_index is not None:
2010
 
            request_lists.append((current_index, current_list))
2011
 
        for index, offsets in request_lists:
2012
 
            try:
2013
 
                transport, path = self._indices[index]
2014
 
            except KeyError:
2015
 
                # A KeyError here indicates that someone has triggered an index
2016
 
                # reload, and this index has gone missing, we need to start
2017
 
                # over.
2018
 
                if self._reload_func is None:
2019
 
                    # If we don't have a _reload_func there is nothing that can
2020
 
                    # be done
2021
 
                    raise
2022
 
                raise errors.RetryWithNewPacks(index,
2023
 
                                               reload_occurred=True,
2024
 
                                               exc_info=sys.exc_info())
2025
 
            try:
2026
 
                reader = pack.make_readv_reader(transport, path, offsets)
2027
 
                for names, read_func in reader.iter_records():
2028
 
                    yield read_func(None)
2029
 
            except errors.NoSuchFile:
2030
 
                # A NoSuchFile error indicates that a pack file has gone
2031
 
                # missing on disk, we need to trigger a reload, and start over.
2032
 
                if self._reload_func is None:
2033
 
                    raise
2034
 
                raise errors.RetryWithNewPacks(transport.abspath(path),
2035
 
                                               reload_occurred=False,
2036
 
                                               exc_info=sys.exc_info())
2037
 
 
2038
 
    def set_writer(self, writer, index, transport_packname):
2039
 
        """Set a writer to use for adding data."""
2040
 
        if index is not None:
2041
 
            self._indices[index] = transport_packname
2042
 
        self._container_writer = writer
2043
 
        self._write_index = index
2044
 
 
2045
 
    def reload_or_raise(self, retry_exc):
2046
 
        """Try calling the reload function, or re-raise the original exception.
2047
 
 
2048
 
        This should be called after _DirectPackAccess raises a
2049
 
        RetryWithNewPacks exception. This function will handle the common logic
2050
 
        of determining when the error is fatal versus being temporary.
2051
 
        It will also make sure that the original exception is raised, rather
2052
 
        than the RetryWithNewPacks exception.
2053
 
 
2054
 
        If this function returns, then the calling function should retry
2055
 
        whatever operation was being performed. Otherwise an exception will
2056
 
        be raised.
2057
 
 
2058
 
        :param retry_exc: A RetryWithNewPacks exception.
2059
 
        """
2060
 
        is_error = False
2061
 
        if self._reload_func is None:
2062
 
            is_error = True
2063
 
        elif not self._reload_func():
2064
 
            # The reload claimed that nothing changed
2065
 
            if not retry_exc.reload_occurred:
2066
 
                # If there wasn't an earlier reload, then we really were
2067
 
                # expecting to find changes. We didn't find them, so this is a
2068
 
                # hard error
2069
 
                is_error = True
2070
 
        if is_error:
2071
 
            exc_class, exc_value, exc_traceback = retry_exc.exc_info
2072
 
            raise exc_class, exc_value, exc_traceback
2073
 
 
2074
 
 
 
2537
class RepositoryFormatKnitPack1(RepositoryFormatPack):
 
2538
    """A no-subtrees parameterized Pack repository.
 
2539
 
 
2540
    This format was introduced in 0.92.
 
2541
    """
 
2542
 
 
2543
    repository_class = KnitPackRepository
 
2544
    _commit_builder_class = PackCommitBuilder
 
2545
    @property
 
2546
    def _serializer(self):
 
2547
        return xml5.serializer_v5
 
2548
    # What index classes to use
 
2549
    index_builder_class = InMemoryGraphIndex
 
2550
    index_class = GraphIndex
 
2551
 
 
2552
    def _get_matching_bzrdir(self):
 
2553
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
 
2554
 
 
2555
    def _ignore_setting_bzrdir(self, format):
 
2556
        pass
 
2557
 
 
2558
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2559
 
 
2560
    def get_format_string(self):
 
2561
        """See RepositoryFormat.get_format_string()."""
 
2562
        return "Bazaar pack repository format 1 (needs bzr 0.92)\n"
 
2563
 
 
2564
    def get_format_description(self):
 
2565
        """See RepositoryFormat.get_format_description()."""
 
2566
        return "Packs containing knits without subtree support"
 
2567
 
 
2568
    def check_conversion_target(self, target_format):
 
2569
        pass
 
2570
 
 
2571
 
 
2572
class RepositoryFormatKnitPack3(RepositoryFormatPack):
 
2573
    """A subtrees parameterized Pack repository.
 
2574
 
 
2575
    This repository format uses the xml7 serializer to get:
 
2576
     - support for recording full info about the tree root
 
2577
     - support for recording tree-references
 
2578
 
 
2579
    This format was introduced in 0.92.
 
2580
    """
 
2581
 
 
2582
    repository_class = KnitPackRepository
 
2583
    _commit_builder_class = PackRootCommitBuilder
 
2584
    rich_root_data = True
 
2585
    supports_tree_reference = True
 
2586
    @property
 
2587
    def _serializer(self):
 
2588
        return xml7.serializer_v7
 
2589
    # What index classes to use
 
2590
    index_builder_class = InMemoryGraphIndex
 
2591
    index_class = GraphIndex
 
2592
 
 
2593
    def _get_matching_bzrdir(self):
 
2594
        return bzrdir.format_registry.make_bzrdir(
 
2595
            'pack-0.92-subtree')
 
2596
 
 
2597
    def _ignore_setting_bzrdir(self, format):
 
2598
        pass
 
2599
 
 
2600
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2601
 
 
2602
    def check_conversion_target(self, target_format):
 
2603
        if not target_format.rich_root_data:
 
2604
            raise errors.BadConversionTarget(
 
2605
                'Does not support rich root data.', target_format)
 
2606
        if not getattr(target_format, 'supports_tree_reference', False):
 
2607
            raise errors.BadConversionTarget(
 
2608
                'Does not support nested trees', target_format)
 
2609
 
 
2610
    def get_format_string(self):
 
2611
        """See RepositoryFormat.get_format_string()."""
 
2612
        return "Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n"
 
2613
 
 
2614
    def get_format_description(self):
 
2615
        """See RepositoryFormat.get_format_description()."""
 
2616
        return "Packs containing knits with subtree support\n"
 
2617
 
 
2618
 
 
2619
class RepositoryFormatKnitPack4(RepositoryFormatPack):
 
2620
    """A rich-root, no subtrees parameterized Pack repository.
 
2621
 
 
2622
    This repository format uses the xml6 serializer to get:
 
2623
     - support for recording full info about the tree root
 
2624
 
 
2625
    This format was introduced in 1.0.
 
2626
    """
 
2627
 
 
2628
    repository_class = KnitPackRepository
 
2629
    _commit_builder_class = PackRootCommitBuilder
 
2630
    rich_root_data = True
 
2631
    supports_tree_reference = False
 
2632
    @property
 
2633
    def _serializer(self):
 
2634
        return xml6.serializer_v6
 
2635
    # What index classes to use
 
2636
    index_builder_class = InMemoryGraphIndex
 
2637
    index_class = GraphIndex
 
2638
 
 
2639
    def _get_matching_bzrdir(self):
 
2640
        return bzrdir.format_registry.make_bzrdir(
 
2641
            'rich-root-pack')
 
2642
 
 
2643
    def _ignore_setting_bzrdir(self, format):
 
2644
        pass
 
2645
 
 
2646
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2647
 
 
2648
    def check_conversion_target(self, target_format):
 
2649
        if not target_format.rich_root_data:
 
2650
            raise errors.BadConversionTarget(
 
2651
                'Does not support rich root data.', target_format)
 
2652
 
 
2653
    def get_format_string(self):
 
2654
        """See RepositoryFormat.get_format_string()."""
 
2655
        return ("Bazaar pack repository format 1 with rich root"
 
2656
                " (needs bzr 1.0)\n")
 
2657
 
 
2658
    def get_format_description(self):
 
2659
        """See RepositoryFormat.get_format_description()."""
 
2660
        return "Packs containing knits with rich root support\n"
 
2661
 
 
2662
 
 
2663
class RepositoryFormatKnitPack5(RepositoryFormatPack):
 
2664
    """Repository that supports external references to allow stacking.
 
2665
 
 
2666
    New in release 1.6.
 
2667
 
 
2668
    Supports external lookups, which results in non-truncated ghosts after
 
2669
    reconcile compared to pack-0.92 formats.
 
2670
    """
 
2671
 
 
2672
    repository_class = KnitPackRepository
 
2673
    _commit_builder_class = PackCommitBuilder
 
2674
    supports_external_lookups = True
 
2675
    # What index classes to use
 
2676
    index_builder_class = InMemoryGraphIndex
 
2677
    index_class = GraphIndex
 
2678
 
 
2679
    @property
 
2680
    def _serializer(self):
 
2681
        return xml5.serializer_v5
 
2682
 
 
2683
    def _get_matching_bzrdir(self):
 
2684
        return bzrdir.format_registry.make_bzrdir('1.6')
 
2685
 
 
2686
    def _ignore_setting_bzrdir(self, format):
 
2687
        pass
 
2688
 
 
2689
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2690
 
 
2691
    def get_format_string(self):
 
2692
        """See RepositoryFormat.get_format_string()."""
 
2693
        return "Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n"
 
2694
 
 
2695
    def get_format_description(self):
 
2696
        """See RepositoryFormat.get_format_description()."""
 
2697
        return "Packs 5 (adds stacking support, requires bzr 1.6)"
 
2698
 
 
2699
    def check_conversion_target(self, target_format):
 
2700
        pass
 
2701
 
 
2702
 
 
2703
class RepositoryFormatKnitPack5RichRoot(RepositoryFormatPack):
 
2704
    """A repository with rich roots and stacking.
 
2705
 
 
2706
    New in release 1.6.1.
 
2707
 
 
2708
    Supports stacking on other repositories, allowing data to be accessed
 
2709
    without being stored locally.
 
2710
    """
 
2711
 
 
2712
    repository_class = KnitPackRepository
 
2713
    _commit_builder_class = PackRootCommitBuilder
 
2714
    rich_root_data = True
 
2715
    supports_tree_reference = False # no subtrees
 
2716
    supports_external_lookups = True
 
2717
    # What index classes to use
 
2718
    index_builder_class = InMemoryGraphIndex
 
2719
    index_class = GraphIndex
 
2720
 
 
2721
    @property
 
2722
    def _serializer(self):
 
2723
        return xml6.serializer_v6
 
2724
 
 
2725
    def _get_matching_bzrdir(self):
 
2726
        return bzrdir.format_registry.make_bzrdir(
 
2727
            '1.6.1-rich-root')
 
2728
 
 
2729
    def _ignore_setting_bzrdir(self, format):
 
2730
        pass
 
2731
 
 
2732
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2733
 
 
2734
    def check_conversion_target(self, target_format):
 
2735
        if not target_format.rich_root_data:
 
2736
            raise errors.BadConversionTarget(
 
2737
                'Does not support rich root data.', target_format)
 
2738
 
 
2739
    def get_format_string(self):
 
2740
        """See RepositoryFormat.get_format_string()."""
 
2741
        return "Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n"
 
2742
 
 
2743
    def get_format_description(self):
 
2744
        return "Packs 5 rich-root (adds stacking support, requires bzr 1.6.1)"
 
2745
 
 
2746
 
 
2747
class RepositoryFormatKnitPack5RichRootBroken(RepositoryFormatPack):
 
2748
    """A repository with rich roots and external references.
 
2749
 
 
2750
    New in release 1.6.
 
2751
 
 
2752
    Supports external lookups, which results in non-truncated ghosts after
 
2753
    reconcile compared to pack-0.92 formats.
 
2754
 
 
2755
    This format was deprecated because the serializer it uses accidentally
 
2756
    supported subtrees, when the format was not intended to. This meant that
 
2757
    someone could accidentally fetch from an incorrect repository.
 
2758
    """
 
2759
 
 
2760
    repository_class = KnitPackRepository
 
2761
    _commit_builder_class = PackRootCommitBuilder
 
2762
    rich_root_data = True
 
2763
    supports_tree_reference = False # no subtrees
 
2764
 
 
2765
    supports_external_lookups = True
 
2766
    # What index classes to use
 
2767
    index_builder_class = InMemoryGraphIndex
 
2768
    index_class = GraphIndex
 
2769
 
 
2770
    @property
 
2771
    def _serializer(self):
 
2772
        return xml7.serializer_v7
 
2773
 
 
2774
    def _get_matching_bzrdir(self):
 
2775
        matching = bzrdir.format_registry.make_bzrdir(
 
2776
            '1.6.1-rich-root')
 
2777
        matching.repository_format = self
 
2778
        return matching
 
2779
 
 
2780
    def _ignore_setting_bzrdir(self, format):
 
2781
        pass
 
2782
 
 
2783
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2784
 
 
2785
    def check_conversion_target(self, target_format):
 
2786
        if not target_format.rich_root_data:
 
2787
            raise errors.BadConversionTarget(
 
2788
                'Does not support rich root data.', target_format)
 
2789
 
 
2790
    def get_format_string(self):
 
2791
        """See RepositoryFormat.get_format_string()."""
 
2792
        return "Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n"
 
2793
 
 
2794
    def get_format_description(self):
 
2795
        return ("Packs 5 rich-root (adds stacking support, requires bzr 1.6)"
 
2796
                " (deprecated)")
 
2797
 
 
2798
 
 
2799
class RepositoryFormatKnitPack6(RepositoryFormatPack):
 
2800
    """A repository with stacking and btree indexes,
 
2801
    without rich roots or subtrees.
 
2802
 
 
2803
    This is equivalent to pack-1.6 with B+Tree indices.
 
2804
    """
 
2805
 
 
2806
    repository_class = KnitPackRepository
 
2807
    _commit_builder_class = PackCommitBuilder
 
2808
    supports_external_lookups = True
 
2809
    # What index classes to use
 
2810
    index_builder_class = BTreeBuilder
 
2811
    index_class = BTreeGraphIndex
 
2812
 
 
2813
    @property
 
2814
    def _serializer(self):
 
2815
        return xml5.serializer_v5
 
2816
 
 
2817
    def _get_matching_bzrdir(self):
 
2818
        return bzrdir.format_registry.make_bzrdir('1.9')
 
2819
 
 
2820
    def _ignore_setting_bzrdir(self, format):
 
2821
        pass
 
2822
 
 
2823
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2824
 
 
2825
    def get_format_string(self):
 
2826
        """See RepositoryFormat.get_format_string()."""
 
2827
        return "Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n"
 
2828
 
 
2829
    def get_format_description(self):
 
2830
        """See RepositoryFormat.get_format_description()."""
 
2831
        return "Packs 6 (uses btree indexes, requires bzr 1.9)"
 
2832
 
 
2833
    def check_conversion_target(self, target_format):
 
2834
        pass
 
2835
 
 
2836
 
 
2837
class RepositoryFormatKnitPack6RichRoot(RepositoryFormatPack):
 
2838
    """A repository with rich roots, no subtrees, stacking and btree indexes.
 
2839
 
 
2840
    1.6-rich-root with B+Tree indices.
 
2841
    """
 
2842
 
 
2843
    repository_class = KnitPackRepository
 
2844
    _commit_builder_class = PackRootCommitBuilder
 
2845
    rich_root_data = True
 
2846
    supports_tree_reference = False # no subtrees
 
2847
    supports_external_lookups = True
 
2848
    # What index classes to use
 
2849
    index_builder_class = BTreeBuilder
 
2850
    index_class = BTreeGraphIndex
 
2851
 
 
2852
    @property
 
2853
    def _serializer(self):
 
2854
        return xml6.serializer_v6
 
2855
 
 
2856
    def _get_matching_bzrdir(self):
 
2857
        return bzrdir.format_registry.make_bzrdir(
 
2858
            '1.9-rich-root')
 
2859
 
 
2860
    def _ignore_setting_bzrdir(self, format):
 
2861
        pass
 
2862
 
 
2863
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2864
 
 
2865
    def check_conversion_target(self, target_format):
 
2866
        if not target_format.rich_root_data:
 
2867
            raise errors.BadConversionTarget(
 
2868
                'Does not support rich root data.', target_format)
 
2869
 
 
2870
    def get_format_string(self):
 
2871
        """See RepositoryFormat.get_format_string()."""
 
2872
        return "Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n"
 
2873
 
 
2874
    def get_format_description(self):
 
2875
        return "Packs 6 rich-root (uses btree indexes, requires bzr 1.9)"
 
2876
 
 
2877
 
 
2878
class RepositoryFormatPackDevelopment2Subtree(RepositoryFormatPack):
 
2879
    """A subtrees development repository.
 
2880
 
 
2881
    This format should be retained until the second release after bzr 1.7.
 
2882
 
 
2883
    1.6.1-subtree[as it might have been] with B+Tree indices.
 
2884
 
 
2885
    This is [now] retained until we have a CHK based subtree format in
 
2886
    development.
 
2887
    """
 
2888
 
 
2889
    repository_class = KnitPackRepository
 
2890
    _commit_builder_class = PackRootCommitBuilder
 
2891
    rich_root_data = True
 
2892
    supports_tree_reference = True
 
2893
    supports_external_lookups = True
 
2894
    # What index classes to use
 
2895
    index_builder_class = BTreeBuilder
 
2896
    index_class = BTreeGraphIndex
 
2897
 
 
2898
    @property
 
2899
    def _serializer(self):
 
2900
        return xml7.serializer_v7
 
2901
 
 
2902
    def _get_matching_bzrdir(self):
 
2903
        return bzrdir.format_registry.make_bzrdir(
 
2904
            'development-subtree')
 
2905
 
 
2906
    def _ignore_setting_bzrdir(self, format):
 
2907
        pass
 
2908
 
 
2909
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2910
 
 
2911
    def check_conversion_target(self, target_format):
 
2912
        if not target_format.rich_root_data:
 
2913
            raise errors.BadConversionTarget(
 
2914
                'Does not support rich root data.', target_format)
 
2915
        if not getattr(target_format, 'supports_tree_reference', False):
 
2916
            raise errors.BadConversionTarget(
 
2917
                'Does not support nested trees', target_format)
 
2918
 
 
2919
    def get_format_string(self):
 
2920
        """See RepositoryFormat.get_format_string()."""
 
2921
        return ("Bazaar development format 2 with subtree support "
 
2922
            "(needs bzr.dev from before 1.8)\n")
 
2923
 
 
2924
    def get_format_description(self):
 
2925
        """See RepositoryFormat.get_format_description()."""
 
2926
        return ("Development repository format, currently the same as "
 
2927
            "1.6.1-subtree with B+Tree indices.\n")
2075
2928