~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: John Ferlito
  • Date: 2009-09-02 04:31:45 UTC
  • mto: (4665.7.1 serve-init)
  • mto: This revision was merged to the branch mainline in revision 4913.
  • Revision ID: johnf@inodes.org-20090902043145-gxdsfw03ilcwbyn5
Add a debian init script for bzr --serve

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