~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: Andrew Bennetts
  • Date: 2010-07-29 11:17:57 UTC
  • mfrom: (5050.3.17 2.2)
  • mto: This revision was merged to the branch mainline in revision 5365.
  • Revision ID: andrew.bennetts@canonical.com-20100729111757-018h3pcefo7z0dnq
Merge lp:bzr/2.2 into lp:bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 Canonical Ltd
 
1
# Copyright (C) 2007-2010 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
25
25
from bzrlib import (
26
26
    chk_map,
27
27
    cleanup,
28
 
    config,
29
28
    debug,
30
29
    graph,
31
30
    osutils,
32
31
    pack,
33
32
    transactions,
34
 
    tsort,
35
33
    ui,
 
34
    xml5,
 
35
    xml6,
 
36
    xml7,
36
37
    )
37
38
from bzrlib.index import (
38
39
    CombinedGraphIndex,
39
40
    GraphIndexPrefixAdapter,
40
41
    )
 
42
from bzrlib.knit import (
 
43
    KnitPlainFactory,
 
44
    KnitVersionedFiles,
 
45
    _KnitGraphIndex,
 
46
    _DirectPackAccess,
 
47
    )
 
48
from bzrlib import tsort
41
49
""")
42
50
from bzrlib import (
43
 
    btree_index,
 
51
    bzrdir,
44
52
    errors,
45
53
    lockable_files,
46
54
    lockdir,
 
55
    revision as _mod_revision,
47
56
    )
48
57
 
49
 
from bzrlib.decorators import (
50
 
    needs_read_lock,
51
 
    needs_write_lock,
52
 
    only_raises,
 
58
from bzrlib.decorators import needs_write_lock, only_raises
 
59
from bzrlib.btree_index import (
 
60
    BTreeGraphIndex,
 
61
    BTreeBuilder,
 
62
    )
 
63
from bzrlib.index import (
 
64
    GraphIndex,
 
65
    InMemoryGraphIndex,
53
66
    )
54
67
from bzrlib.lock import LogicalLockResult
 
68
from bzrlib.repofmt.knitrepo import KnitRepository
55
69
from bzrlib.repository import (
56
 
    _LazyListJoin,
57
 
    MetaDirRepository,
 
70
    CommitBuilder,
 
71
    MetaDirRepositoryFormat,
58
72
    RepositoryFormat,
59
73
    RepositoryWriteLockResult,
60
 
    )
61
 
from bzrlib.vf_repository import (
62
 
    MetaDirVersionedFileRepository,
63
 
    MetaDirVersionedFileRepositoryFormat,
64
 
    VersionedFileCommitBuilder,
65
 
    VersionedFileRootCommitBuilder,
 
74
    RootCommitBuilder,
 
75
    StreamSource,
66
76
    )
67
77
from bzrlib.trace import (
68
78
    mutter,
71
81
    )
72
82
 
73
83
 
74
 
class PackCommitBuilder(VersionedFileCommitBuilder):
75
 
    """Subclass of VersionedFileCommitBuilder to add texts with pack semantics.
 
84
class PackCommitBuilder(CommitBuilder):
 
85
    """A subclass of CommitBuilder to add texts with pack semantics.
76
86
 
77
87
    Specifically this uses one knit object rather than one knit object per
78
88
    added text, reducing memory and object pressure.
80
90
 
81
91
    def __init__(self, repository, parents, config, timestamp=None,
82
92
                 timezone=None, committer=None, revprops=None,
83
 
                 revision_id=None, lossy=False):
84
 
        VersionedFileCommitBuilder.__init__(self, repository, parents, config,
 
93
                 revision_id=None):
 
94
        CommitBuilder.__init__(self, repository, parents, config,
85
95
            timestamp=timestamp, timezone=timezone, committer=committer,
86
 
            revprops=revprops, revision_id=revision_id, lossy=lossy)
 
96
            revprops=revprops, revision_id=revision_id)
87
97
        self._file_graph = graph.Graph(
88
98
            repository._pack_collection.text_index.combined_index)
89
99
 
92
102
        return set([key[1] for key in self._file_graph.heads(keys)])
93
103
 
94
104
 
95
 
class PackRootCommitBuilder(VersionedFileRootCommitBuilder):
 
105
class PackRootCommitBuilder(RootCommitBuilder):
96
106
    """A subclass of RootCommitBuilder to add texts with pack semantics.
97
107
 
98
108
    Specifically this uses one knit object rather than one knit object per
101
111
 
102
112
    def __init__(self, repository, parents, config, timestamp=None,
103
113
                 timezone=None, committer=None, revprops=None,
104
 
                 revision_id=None, lossy=False):
105
 
        super(PackRootCommitBuilder, self).__init__(repository, parents,
106
 
            config, timestamp=timestamp, timezone=timezone,
107
 
            committer=committer, revprops=revprops, revision_id=revision_id,
108
 
            lossy=lossy)
 
114
                 revision_id=None):
 
115
        CommitBuilder.__init__(self, repository, parents, config,
 
116
            timestamp=timestamp, timezone=timezone, committer=committer,
 
117
            revprops=revprops, revision_id=revision_id)
109
118
        self._file_graph = graph.Graph(
110
119
            repository._pack_collection.text_index.combined_index)
111
120
 
222
231
        unlimited_cache = False
223
232
        if index_type == 'chk':
224
233
            unlimited_cache = True
225
 
        index = self.index_class(self.index_transport,
226
 
                    self.index_name(index_type, self.name),
227
 
                    self.index_sizes[self.index_offset(index_type)],
228
 
                    unlimited_cache=unlimited_cache)
229
 
        if index_type == 'chk':
230
 
            index._leaf_factory = btree_index._gcchk_factory
231
 
        setattr(self, index_type + '_index', index)
 
234
        setattr(self, index_type + '_index',
 
235
            self.index_class(self.index_transport,
 
236
                self.index_name(index_type, self.name),
 
237
                self.index_sizes[self.index_offset(index_type)],
 
238
                unlimited_cache=unlimited_cache))
232
239
 
233
240
 
234
241
class ExistingPack(Pack):
315
322
        for index_type in index_types:
316
323
            old_name = self.index_name(index_type, self.name)
317
324
            new_name = '../indices/' + old_name
318
 
            self.upload_transport.move(old_name, new_name)
 
325
            self.upload_transport.rename(old_name, new_name)
319
326
            self._replace_index_with_readonly(index_type)
320
327
        new_name = '../packs/' + self.file_name()
321
 
        self.upload_transport.move(self.file_name(), new_name)
 
328
        self.upload_transport.rename(self.file_name(), new_name)
322
329
        self._state = 'finished'
323
330
 
324
331
    def _get_external_refs(self, index):
479
486
        # visible is smaller.  On the other hand none will be seen until
480
487
        # they're in the names list.
481
488
        self.index_sizes = [None, None, None, None]
482
 
        self._write_index('revision', self.revision_index, 'revision',
483
 
            suspend)
 
489
        self._write_index('revision', self.revision_index, 'revision', suspend)
484
490
        self._write_index('inventory', self.inventory_index, 'inventory',
485
491
            suspend)
486
492
        self._write_index('text', self.text_index, 'file texts', suspend)
490
496
            self.index_sizes.append(None)
491
497
            self._write_index('chk', self.chk_index,
492
498
                'content hash bytes', suspend)
493
 
        self.write_stream.close(
494
 
            want_fdatasync=self._pack_collection.config_stack.get('repository.fdatasync'))
 
499
        self.write_stream.close()
495
500
        # Note that this will clobber an existing pack with the same name,
496
501
        # without checking for hash collisions. While this is undesirable this
497
502
        # is something that can be rectified in a subsequent release. One way
506
511
        new_name = self.name + '.pack'
507
512
        if not suspend:
508
513
            new_name = '../packs/' + new_name
509
 
        self.upload_transport.move(self.random_name, new_name)
 
514
        self.upload_transport.rename(self.random_name, new_name)
510
515
        self._state = 'finished'
511
516
        if 'pack' in debug.debug_flags:
512
517
            # XXX: size might be interesting?
540
545
            transport = self.upload_transport
541
546
        else:
542
547
            transport = self.index_transport
543
 
        index_tempfile = index.finish()
544
 
        index_bytes = index_tempfile.read()
545
 
        write_stream = transport.open_write_stream(index_name,
546
 
            mode=self._file_mode)
547
 
        write_stream.write(index_bytes)
548
 
        write_stream.close(
549
 
            want_fdatasync=self._pack_collection.config_stack.get('repository.fdatasync'))
550
 
        self.index_sizes[self.index_offset(index_type)] = len(index_bytes)
 
548
        self.index_sizes[self.index_offset(index_type)] = transport.put_file(
 
549
            index_name, index.finish(), mode=self._file_mode)
551
550
        if 'pack' in debug.debug_flags:
552
551
            # XXX: size might be interesting?
553
552
            mutter('%s: create_pack: wrote %s index: %s%s t+%6.3fs',
675
674
        # What text keys to copy. None for 'all texts'. This is set by
676
675
        # _copy_inventory_texts
677
676
        self._text_filter = None
 
677
        self._extra_init()
 
678
 
 
679
    def _extra_init(self):
 
680
        """A template hook to allow extending the constructor trivially."""
 
681
 
 
682
    def _pack_map_and_index_list(self, index_attribute):
 
683
        """Convert a list of packs to an index pack map and index list.
 
684
 
 
685
        :param index_attribute: The attribute that the desired index is found
 
686
            on.
 
687
        :return: A tuple (map, list) where map contains the dict from
 
688
            index:pack_tuple, and list contains the indices in the preferred
 
689
            access order.
 
690
        """
 
691
        indices = []
 
692
        pack_map = {}
 
693
        for pack_obj in self.packs:
 
694
            index = getattr(pack_obj, index_attribute)
 
695
            indices.append(index)
 
696
            pack_map[index] = pack_obj
 
697
        return pack_map, indices
 
698
 
 
699
    def _index_contents(self, indices, key_filter=None):
 
700
        """Get an iterable of the index contents from a pack_map.
 
701
 
 
702
        :param indices: The list of indices to query
 
703
        :param key_filter: An optional filter to limit the keys returned.
 
704
        """
 
705
        all_index = CombinedGraphIndex(indices)
 
706
        if key_filter is None:
 
707
            return all_index.iter_all_entries()
 
708
        else:
 
709
            return all_index.iter_entries(key_filter)
678
710
 
679
711
    def pack(self, pb=None):
680
712
        """Create a new pack by reading data from other packs.
691
723
        :return: A Pack object, or None if nothing was copied.
692
724
        """
693
725
        # open a pack - using the same name as the last temporary file
694
 
        # - which has already been flushed, so it's safe.
 
726
        # - which has already been flushed, so its safe.
695
727
        # XXX: - duplicate code warning with start_write_group; fix before
696
728
        #      considering 'done'.
697
729
        if self._pack_collection._new_pack is not None:
729
761
        new_pack.signature_index.set_optimize(combine_backing_indices=False)
730
762
        return new_pack
731
763
 
 
764
    def _update_pack_order(self, entries, index_to_pack_map):
 
765
        """Determine how we want our packs to be ordered.
 
766
 
 
767
        This changes the sort order of the self.packs list so that packs unused
 
768
        by 'entries' will be at the end of the list, so that future requests
 
769
        can avoid probing them.  Used packs will be at the front of the
 
770
        self.packs list, in the order of their first use in 'entries'.
 
771
 
 
772
        :param entries: A list of (index, ...) tuples
 
773
        :param index_to_pack_map: A mapping from index objects to pack objects.
 
774
        """
 
775
        packs = []
 
776
        seen_indexes = set()
 
777
        for entry in entries:
 
778
            index = entry[0]
 
779
            if index not in seen_indexes:
 
780
                packs.append(index_to_pack_map[index])
 
781
                seen_indexes.add(index)
 
782
        if len(packs) == len(self.packs):
 
783
            if 'pack' in debug.debug_flags:
 
784
                mutter('Not changing pack list, all packs used.')
 
785
            return
 
786
        seen_packs = set(packs)
 
787
        for pack in self.packs:
 
788
            if pack not in seen_packs:
 
789
                packs.append(pack)
 
790
                seen_packs.add(pack)
 
791
        if 'pack' in debug.debug_flags:
 
792
            old_names = [p.access_tuple()[1] for p in self.packs]
 
793
            new_names = [p.access_tuple()[1] for p in packs]
 
794
            mutter('Reordering packs\nfrom: %s\n  to: %s',
 
795
                   old_names, new_names)
 
796
        self.packs = packs
 
797
 
732
798
    def _copy_revision_texts(self):
733
799
        """Copy revision data to the new pack."""
734
 
        raise NotImplementedError(self._copy_revision_texts)
 
800
        # select revisions
 
801
        if self.revision_ids:
 
802
            revision_keys = [(revision_id,) for revision_id in self.revision_ids]
 
803
        else:
 
804
            revision_keys = None
 
805
        # select revision keys
 
806
        revision_index_map, revision_indices = self._pack_map_and_index_list(
 
807
            'revision_index')
 
808
        revision_nodes = self._index_contents(revision_indices, revision_keys)
 
809
        revision_nodes = list(revision_nodes)
 
810
        self._update_pack_order(revision_nodes, revision_index_map)
 
811
        # copy revision keys and adjust values
 
812
        self.pb.update("Copying revision texts", 1)
 
813
        total_items, readv_group_iter = self._revision_node_readv(revision_nodes)
 
814
        list(self._copy_nodes_graph(revision_index_map, self.new_pack._writer,
 
815
            self.new_pack.revision_index, readv_group_iter, total_items))
 
816
        if 'pack' in debug.debug_flags:
 
817
            mutter('%s: create_pack: revisions copied: %s%s %d items t+%6.3fs',
 
818
                time.ctime(), self._pack_collection._upload_transport.base,
 
819
                self.new_pack.random_name,
 
820
                self.new_pack.revision_index.key_count(),
 
821
                time.time() - self.new_pack.start_time)
 
822
        self._revision_keys = revision_keys
735
823
 
736
824
    def _copy_inventory_texts(self):
737
825
        """Copy the inventory texts to the new pack.
740
828
 
741
829
        Sets self._text_filter appropriately.
742
830
        """
743
 
        raise NotImplementedError(self._copy_inventory_texts)
 
831
        # select inventory keys
 
832
        inv_keys = self._revision_keys # currently the same keyspace, and note that
 
833
        # querying for keys here could introduce a bug where an inventory item
 
834
        # is missed, so do not change it to query separately without cross
 
835
        # checking like the text key check below.
 
836
        inventory_index_map, inventory_indices = self._pack_map_and_index_list(
 
837
            'inventory_index')
 
838
        inv_nodes = self._index_contents(inventory_indices, inv_keys)
 
839
        # copy inventory keys and adjust values
 
840
        # XXX: Should be a helper function to allow different inv representation
 
841
        # at this point.
 
842
        self.pb.update("Copying inventory texts", 2)
 
843
        total_items, readv_group_iter = self._least_readv_node_readv(inv_nodes)
 
844
        # Only grab the output lines if we will be processing them
 
845
        output_lines = bool(self.revision_ids)
 
846
        inv_lines = self._copy_nodes_graph(inventory_index_map,
 
847
            self.new_pack._writer, self.new_pack.inventory_index,
 
848
            readv_group_iter, total_items, output_lines=output_lines)
 
849
        if self.revision_ids:
 
850
            self._process_inventory_lines(inv_lines)
 
851
        else:
 
852
            # eat the iterator to cause it to execute.
 
853
            list(inv_lines)
 
854
            self._text_filter = None
 
855
        if 'pack' in debug.debug_flags:
 
856
            mutter('%s: create_pack: inventories copied: %s%s %d items t+%6.3fs',
 
857
                time.ctime(), self._pack_collection._upload_transport.base,
 
858
                self.new_pack.random_name,
 
859
                self.new_pack.inventory_index.key_count(),
 
860
                time.time() - self.new_pack.start_time)
744
861
 
745
862
    def _copy_text_texts(self):
746
 
        raise NotImplementedError(self._copy_text_texts)
 
863
        # select text keys
 
864
        text_index_map, text_nodes = self._get_text_nodes()
 
865
        if self._text_filter is not None:
 
866
            # We could return the keys copied as part of the return value from
 
867
            # _copy_nodes_graph but this doesn't work all that well with the
 
868
            # need to get line output too, so we check separately, and as we're
 
869
            # going to buffer everything anyway, we check beforehand, which
 
870
            # saves reading knit data over the wire when we know there are
 
871
            # mising records.
 
872
            text_nodes = set(text_nodes)
 
873
            present_text_keys = set(_node[1] for _node in text_nodes)
 
874
            missing_text_keys = set(self._text_filter) - present_text_keys
 
875
            if missing_text_keys:
 
876
                # TODO: raise a specific error that can handle many missing
 
877
                # keys.
 
878
                mutter("missing keys during fetch: %r", missing_text_keys)
 
879
                a_missing_key = missing_text_keys.pop()
 
880
                raise errors.RevisionNotPresent(a_missing_key[1],
 
881
                    a_missing_key[0])
 
882
        # copy text keys and adjust values
 
883
        self.pb.update("Copying content texts", 3)
 
884
        total_items, readv_group_iter = self._least_readv_node_readv(text_nodes)
 
885
        list(self._copy_nodes_graph(text_index_map, self.new_pack._writer,
 
886
            self.new_pack.text_index, readv_group_iter, total_items))
 
887
        self._log_copied_texts()
747
888
 
748
889
    def _create_pack_from_packs(self):
749
 
        raise NotImplementedError(self._create_pack_from_packs)
 
890
        self.pb.update("Opening pack", 0, 5)
 
891
        self.new_pack = self.open_pack()
 
892
        new_pack = self.new_pack
 
893
        # buffer data - we won't be reading-back during the pack creation and
 
894
        # this makes a significant difference on sftp pushes.
 
895
        new_pack.set_write_cache_size(1024*1024)
 
896
        if 'pack' in debug.debug_flags:
 
897
            plain_pack_list = ['%s%s' % (a_pack.pack_transport.base, a_pack.name)
 
898
                for a_pack in self.packs]
 
899
            if self.revision_ids is not None:
 
900
                rev_count = len(self.revision_ids)
 
901
            else:
 
902
                rev_count = 'all'
 
903
            mutter('%s: create_pack: creating pack from source packs: '
 
904
                '%s%s %s revisions wanted %s t=0',
 
905
                time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
 
906
                plain_pack_list, rev_count)
 
907
        self._copy_revision_texts()
 
908
        self._copy_inventory_texts()
 
909
        self._copy_text_texts()
 
910
        # select signature keys
 
911
        signature_filter = self._revision_keys # same keyspace
 
912
        signature_index_map, signature_indices = self._pack_map_and_index_list(
 
913
            'signature_index')
 
914
        signature_nodes = self._index_contents(signature_indices,
 
915
            signature_filter)
 
916
        # copy signature keys and adjust values
 
917
        self.pb.update("Copying signature texts", 4)
 
918
        self._copy_nodes(signature_nodes, signature_index_map, new_pack._writer,
 
919
            new_pack.signature_index)
 
920
        if 'pack' in debug.debug_flags:
 
921
            mutter('%s: create_pack: revision signatures copied: %s%s %d items t+%6.3fs',
 
922
                time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
 
923
                new_pack.signature_index.key_count(),
 
924
                time.time() - new_pack.start_time)
 
925
        # copy chk contents
 
926
        # NB XXX: how to check CHK references are present? perhaps by yielding
 
927
        # the items? How should that interact with stacked repos?
 
928
        if new_pack.chk_index is not None:
 
929
            self._copy_chks()
 
930
            if 'pack' in debug.debug_flags:
 
931
                mutter('%s: create_pack: chk content copied: %s%s %d items t+%6.3fs',
 
932
                    time.ctime(), self._pack_collection._upload_transport.base,
 
933
                    new_pack.random_name,
 
934
                    new_pack.chk_index.key_count(),
 
935
                    time.time() - new_pack.start_time)
 
936
        new_pack._check_references()
 
937
        if not self._use_pack(new_pack):
 
938
            new_pack.abort()
 
939
            return None
 
940
        self.pb.update("Finishing pack", 5)
 
941
        new_pack.finish()
 
942
        self._pack_collection.allocate(new_pack)
 
943
        return new_pack
 
944
 
 
945
    def _copy_chks(self, refs=None):
 
946
        # XXX: Todo, recursive follow-pointers facility when fetching some
 
947
        # revisions only.
 
948
        chk_index_map, chk_indices = self._pack_map_and_index_list(
 
949
            'chk_index')
 
950
        chk_nodes = self._index_contents(chk_indices, refs)
 
951
        new_refs = set()
 
952
        # TODO: This isn't strictly tasteful as we are accessing some private
 
953
        #       variables (_serializer). Perhaps a better way would be to have
 
954
        #       Repository._deserialise_chk_node()
 
955
        search_key_func = chk_map.search_key_registry.get(
 
956
            self._pack_collection.repo._serializer.search_key_name)
 
957
        def accumlate_refs(lines):
 
958
            # XXX: move to a generic location
 
959
            # Yay mismatch:
 
960
            bytes = ''.join(lines)
 
961
            node = chk_map._deserialise(bytes, ("unknown",), search_key_func)
 
962
            new_refs.update(node.refs())
 
963
        self._copy_nodes(chk_nodes, chk_index_map, self.new_pack._writer,
 
964
            self.new_pack.chk_index, output_lines=accumlate_refs)
 
965
        return new_refs
 
966
 
 
967
    def _copy_nodes(self, nodes, index_map, writer, write_index,
 
968
        output_lines=None):
 
969
        """Copy knit nodes between packs with no graph references.
 
970
 
 
971
        :param output_lines: Output full texts of copied items.
 
972
        """
 
973
        pb = ui.ui_factory.nested_progress_bar()
 
974
        try:
 
975
            return self._do_copy_nodes(nodes, index_map, writer,
 
976
                write_index, pb, output_lines=output_lines)
 
977
        finally:
 
978
            pb.finished()
 
979
 
 
980
    def _do_copy_nodes(self, nodes, index_map, writer, write_index, pb,
 
981
        output_lines=None):
 
982
        # for record verification
 
983
        knit = KnitVersionedFiles(None, None)
 
984
        # plan a readv on each source pack:
 
985
        # group by pack
 
986
        nodes = sorted(nodes)
 
987
        # how to map this into knit.py - or knit.py into this?
 
988
        # we don't want the typical knit logic, we want grouping by pack
 
989
        # at this point - perhaps a helper library for the following code
 
990
        # duplication points?
 
991
        request_groups = {}
 
992
        for index, key, value in nodes:
 
993
            if index not in request_groups:
 
994
                request_groups[index] = []
 
995
            request_groups[index].append((key, value))
 
996
        record_index = 0
 
997
        pb.update("Copied record", record_index, len(nodes))
 
998
        for index, items in request_groups.iteritems():
 
999
            pack_readv_requests = []
 
1000
            for key, value in items:
 
1001
                # ---- KnitGraphIndex.get_position
 
1002
                bits = value[1:].split(' ')
 
1003
                offset, length = int(bits[0]), int(bits[1])
 
1004
                pack_readv_requests.append((offset, length, (key, value[0])))
 
1005
            # linear scan up the pack
 
1006
            pack_readv_requests.sort()
 
1007
            # copy the data
 
1008
            pack_obj = index_map[index]
 
1009
            transport, path = pack_obj.access_tuple()
 
1010
            try:
 
1011
                reader = pack.make_readv_reader(transport, path,
 
1012
                    [offset[0:2] for offset in pack_readv_requests])
 
1013
            except errors.NoSuchFile:
 
1014
                if self._reload_func is not None:
 
1015
                    self._reload_func()
 
1016
                raise
 
1017
            for (names, read_func), (_1, _2, (key, eol_flag)) in \
 
1018
                izip(reader.iter_records(), pack_readv_requests):
 
1019
                raw_data = read_func(None)
 
1020
                # check the header only
 
1021
                if output_lines is not None:
 
1022
                    output_lines(knit._parse_record(key[-1], raw_data)[0])
 
1023
                else:
 
1024
                    df, _ = knit._parse_record_header(key, raw_data)
 
1025
                    df.close()
 
1026
                pos, size = writer.add_bytes_record(raw_data, names)
 
1027
                write_index.add_node(key, eol_flag + "%d %d" % (pos, size))
 
1028
                pb.update("Copied record", record_index)
 
1029
                record_index += 1
 
1030
 
 
1031
    def _copy_nodes_graph(self, index_map, writer, write_index,
 
1032
        readv_group_iter, total_items, output_lines=False):
 
1033
        """Copy knit nodes between packs.
 
1034
 
 
1035
        :param output_lines: Return lines present in the copied data as
 
1036
            an iterator of line,version_id.
 
1037
        """
 
1038
        pb = ui.ui_factory.nested_progress_bar()
 
1039
        try:
 
1040
            for result in self._do_copy_nodes_graph(index_map, writer,
 
1041
                write_index, output_lines, pb, readv_group_iter, total_items):
 
1042
                yield result
 
1043
        except Exception:
 
1044
            # Python 2.4 does not permit try:finally: in a generator.
 
1045
            pb.finished()
 
1046
            raise
 
1047
        else:
 
1048
            pb.finished()
 
1049
 
 
1050
    def _do_copy_nodes_graph(self, index_map, writer, write_index,
 
1051
        output_lines, pb, readv_group_iter, total_items):
 
1052
        # for record verification
 
1053
        knit = KnitVersionedFiles(None, None)
 
1054
        # for line extraction when requested (inventories only)
 
1055
        if output_lines:
 
1056
            factory = KnitPlainFactory()
 
1057
        record_index = 0
 
1058
        pb.update("Copied record", record_index, total_items)
 
1059
        for index, readv_vector, node_vector in readv_group_iter:
 
1060
            # copy the data
 
1061
            pack_obj = index_map[index]
 
1062
            transport, path = pack_obj.access_tuple()
 
1063
            try:
 
1064
                reader = pack.make_readv_reader(transport, path, readv_vector)
 
1065
            except errors.NoSuchFile:
 
1066
                if self._reload_func is not None:
 
1067
                    self._reload_func()
 
1068
                raise
 
1069
            for (names, read_func), (key, eol_flag, references) in \
 
1070
                izip(reader.iter_records(), node_vector):
 
1071
                raw_data = read_func(None)
 
1072
                if output_lines:
 
1073
                    # read the entire thing
 
1074
                    content, _ = knit._parse_record(key[-1], raw_data)
 
1075
                    if len(references[-1]) == 0:
 
1076
                        line_iterator = factory.get_fulltext_content(content)
 
1077
                    else:
 
1078
                        line_iterator = factory.get_linedelta_content(content)
 
1079
                    for line in line_iterator:
 
1080
                        yield line, key
 
1081
                else:
 
1082
                    # check the header only
 
1083
                    df, _ = knit._parse_record_header(key, raw_data)
 
1084
                    df.close()
 
1085
                pos, size = writer.add_bytes_record(raw_data, names)
 
1086
                write_index.add_node(key, eol_flag + "%d %d" % (pos, size), references)
 
1087
                pb.update("Copied record", record_index)
 
1088
                record_index += 1
 
1089
 
 
1090
    def _get_text_nodes(self):
 
1091
        text_index_map, text_indices = self._pack_map_and_index_list(
 
1092
            'text_index')
 
1093
        return text_index_map, self._index_contents(text_indices,
 
1094
            self._text_filter)
 
1095
 
 
1096
    def _least_readv_node_readv(self, nodes):
 
1097
        """Generate request groups for nodes using the least readv's.
 
1098
 
 
1099
        :param nodes: An iterable of graph index nodes.
 
1100
        :return: Total node count and an iterator of the data needed to perform
 
1101
            readvs to obtain the data for nodes. Each item yielded by the
 
1102
            iterator is a tuple with:
 
1103
            index, readv_vector, node_vector. readv_vector is a list ready to
 
1104
            hand to the transport readv method, and node_vector is a list of
 
1105
            (key, eol_flag, references) for the node retrieved by the
 
1106
            matching readv_vector.
 
1107
        """
 
1108
        # group by pack so we do one readv per pack
 
1109
        nodes = sorted(nodes)
 
1110
        total = len(nodes)
 
1111
        request_groups = {}
 
1112
        for index, key, value, references in nodes:
 
1113
            if index not in request_groups:
 
1114
                request_groups[index] = []
 
1115
            request_groups[index].append((key, value, references))
 
1116
        result = []
 
1117
        for index, items in request_groups.iteritems():
 
1118
            pack_readv_requests = []
 
1119
            for key, value, references in items:
 
1120
                # ---- KnitGraphIndex.get_position
 
1121
                bits = value[1:].split(' ')
 
1122
                offset, length = int(bits[0]), int(bits[1])
 
1123
                pack_readv_requests.append(
 
1124
                    ((offset, length), (key, value[0], references)))
 
1125
            # linear scan up the pack to maximum range combining.
 
1126
            pack_readv_requests.sort()
 
1127
            # split out the readv and the node data.
 
1128
            pack_readv = [readv for readv, node in pack_readv_requests]
 
1129
            node_vector = [node for readv, node in pack_readv_requests]
 
1130
            result.append((index, pack_readv, node_vector))
 
1131
        return total, result
750
1132
 
751
1133
    def _log_copied_texts(self):
752
1134
        if 'pack' in debug.debug_flags:
756
1138
                self.new_pack.text_index.key_count(),
757
1139
                time.time() - self.new_pack.start_time)
758
1140
 
 
1141
    def _process_inventory_lines(self, inv_lines):
 
1142
        """Use up the inv_lines generator and setup a text key filter."""
 
1143
        repo = self._pack_collection.repo
 
1144
        fileid_revisions = repo._find_file_ids_from_xml_inventory_lines(
 
1145
            inv_lines, self.revision_keys)
 
1146
        text_filter = []
 
1147
        for fileid, file_revids in fileid_revisions.iteritems():
 
1148
            text_filter.extend([(fileid, file_revid) for file_revid in file_revids])
 
1149
        self._text_filter = text_filter
 
1150
 
 
1151
    def _revision_node_readv(self, revision_nodes):
 
1152
        """Return the total revisions and the readv's to issue.
 
1153
 
 
1154
        :param revision_nodes: The revision index contents for the packs being
 
1155
            incorporated into the new pack.
 
1156
        :return: As per _least_readv_node_readv.
 
1157
        """
 
1158
        return self._least_readv_node_readv(revision_nodes)
 
1159
 
759
1160
    def _use_pack(self, new_pack):
760
1161
        """Return True if new_pack should be used.
761
1162
 
765
1166
        return new_pack.data_inserted()
766
1167
 
767
1168
 
 
1169
class OptimisingPacker(Packer):
 
1170
    """A packer which spends more time to create better disk layouts."""
 
1171
 
 
1172
    def _revision_node_readv(self, revision_nodes):
 
1173
        """Return the total revisions and the readv's to issue.
 
1174
 
 
1175
        This sort places revisions in topological order with the ancestors
 
1176
        after the children.
 
1177
 
 
1178
        :param revision_nodes: The revision index contents for the packs being
 
1179
            incorporated into the new pack.
 
1180
        :return: As per _least_readv_node_readv.
 
1181
        """
 
1182
        # build an ancestors dict
 
1183
        ancestors = {}
 
1184
        by_key = {}
 
1185
        for index, key, value, references in revision_nodes:
 
1186
            ancestors[key] = references[0]
 
1187
            by_key[key] = (index, value, references)
 
1188
        order = tsort.topo_sort(ancestors)
 
1189
        total = len(order)
 
1190
        # Single IO is pathological, but it will work as a starting point.
 
1191
        requests = []
 
1192
        for key in reversed(order):
 
1193
            index, value, references = by_key[key]
 
1194
            # ---- KnitGraphIndex.get_position
 
1195
            bits = value[1:].split(' ')
 
1196
            offset, length = int(bits[0]), int(bits[1])
 
1197
            requests.append(
 
1198
                (index, [(offset, length)], [(key, value[0], references)]))
 
1199
        # TODO: combine requests in the same index that are in ascending order.
 
1200
        return total, requests
 
1201
 
 
1202
    def open_pack(self):
 
1203
        """Open a pack for the pack we are creating."""
 
1204
        new_pack = super(OptimisingPacker, self).open_pack()
 
1205
        # Turn on the optimization flags for all the index builders.
 
1206
        new_pack.revision_index.set_optimize(for_size=True)
 
1207
        new_pack.inventory_index.set_optimize(for_size=True)
 
1208
        new_pack.text_index.set_optimize(for_size=True)
 
1209
        new_pack.signature_index.set_optimize(for_size=True)
 
1210
        return new_pack
 
1211
 
 
1212
 
 
1213
class ReconcilePacker(Packer):
 
1214
    """A packer which regenerates indices etc as it copies.
 
1215
 
 
1216
    This is used by ``bzr reconcile`` to cause parent text pointers to be
 
1217
    regenerated.
 
1218
    """
 
1219
 
 
1220
    def _extra_init(self):
 
1221
        self._data_changed = False
 
1222
 
 
1223
    def _process_inventory_lines(self, inv_lines):
 
1224
        """Generate a text key reference map rather for reconciling with."""
 
1225
        repo = self._pack_collection.repo
 
1226
        refs = repo._find_text_key_references_from_xml_inventory_lines(
 
1227
            inv_lines)
 
1228
        self._text_refs = refs
 
1229
        # during reconcile we:
 
1230
        #  - convert unreferenced texts to full texts
 
1231
        #  - correct texts which reference a text not copied to be full texts
 
1232
        #  - copy all others as-is but with corrected parents.
 
1233
        #  - so at this point we don't know enough to decide what becomes a full
 
1234
        #    text.
 
1235
        self._text_filter = None
 
1236
 
 
1237
    def _copy_text_texts(self):
 
1238
        """generate what texts we should have and then copy."""
 
1239
        self.pb.update("Copying content texts", 3)
 
1240
        # we have three major tasks here:
 
1241
        # 1) generate the ideal index
 
1242
        repo = self._pack_collection.repo
 
1243
        ancestors = dict([(key[0], tuple(ref[0] for ref in refs[0])) for
 
1244
            _1, key, _2, refs in
 
1245
            self.new_pack.revision_index.iter_all_entries()])
 
1246
        ideal_index = repo._generate_text_key_index(self._text_refs, ancestors)
 
1247
        # 2) generate a text_nodes list that contains all the deltas that can
 
1248
        #    be used as-is, with corrected parents.
 
1249
        ok_nodes = []
 
1250
        bad_texts = []
 
1251
        discarded_nodes = []
 
1252
        NULL_REVISION = _mod_revision.NULL_REVISION
 
1253
        text_index_map, text_nodes = self._get_text_nodes()
 
1254
        for node in text_nodes:
 
1255
            # 0 - index
 
1256
            # 1 - key
 
1257
            # 2 - value
 
1258
            # 3 - refs
 
1259
            try:
 
1260
                ideal_parents = tuple(ideal_index[node[1]])
 
1261
            except KeyError:
 
1262
                discarded_nodes.append(node)
 
1263
                self._data_changed = True
 
1264
            else:
 
1265
                if ideal_parents == (NULL_REVISION,):
 
1266
                    ideal_parents = ()
 
1267
                if ideal_parents == node[3][0]:
 
1268
                    # no change needed.
 
1269
                    ok_nodes.append(node)
 
1270
                elif ideal_parents[0:1] == node[3][0][0:1]:
 
1271
                    # the left most parent is the same, or there are no parents
 
1272
                    # today. Either way, we can preserve the representation as
 
1273
                    # long as we change the refs to be inserted.
 
1274
                    self._data_changed = True
 
1275
                    ok_nodes.append((node[0], node[1], node[2],
 
1276
                        (ideal_parents, node[3][1])))
 
1277
                    self._data_changed = True
 
1278
                else:
 
1279
                    # Reinsert this text completely
 
1280
                    bad_texts.append((node[1], ideal_parents))
 
1281
                    self._data_changed = True
 
1282
        # we're finished with some data.
 
1283
        del ideal_index
 
1284
        del text_nodes
 
1285
        # 3) bulk copy the ok data
 
1286
        total_items, readv_group_iter = self._least_readv_node_readv(ok_nodes)
 
1287
        list(self._copy_nodes_graph(text_index_map, self.new_pack._writer,
 
1288
            self.new_pack.text_index, readv_group_iter, total_items))
 
1289
        # 4) adhoc copy all the other texts.
 
1290
        # We have to topologically insert all texts otherwise we can fail to
 
1291
        # reconcile when parts of a single delta chain are preserved intact,
 
1292
        # and other parts are not. E.g. Discarded->d1->d2->d3. d1 will be
 
1293
        # reinserted, and if d3 has incorrect parents it will also be
 
1294
        # reinserted. If we insert d3 first, d2 is present (as it was bulk
 
1295
        # copied), so we will try to delta, but d2 is not currently able to be
 
1296
        # extracted because it's basis d1 is not present. Topologically sorting
 
1297
        # addresses this. The following generates a sort for all the texts that
 
1298
        # are being inserted without having to reference the entire text key
 
1299
        # space (we only topo sort the revisions, which is smaller).
 
1300
        topo_order = tsort.topo_sort(ancestors)
 
1301
        rev_order = dict(zip(topo_order, range(len(topo_order))))
 
1302
        bad_texts.sort(key=lambda key:rev_order.get(key[0][1], 0))
 
1303
        transaction = repo.get_transaction()
 
1304
        file_id_index = GraphIndexPrefixAdapter(
 
1305
            self.new_pack.text_index,
 
1306
            ('blank', ), 1,
 
1307
            add_nodes_callback=self.new_pack.text_index.add_nodes)
 
1308
        data_access = _DirectPackAccess(
 
1309
                {self.new_pack.text_index:self.new_pack.access_tuple()})
 
1310
        data_access.set_writer(self.new_pack._writer, self.new_pack.text_index,
 
1311
            self.new_pack.access_tuple())
 
1312
        output_texts = KnitVersionedFiles(
 
1313
            _KnitGraphIndex(self.new_pack.text_index,
 
1314
                add_callback=self.new_pack.text_index.add_nodes,
 
1315
                deltas=True, parents=True, is_locked=repo.is_locked),
 
1316
            data_access=data_access, max_delta_chain=200)
 
1317
        for key, parent_keys in bad_texts:
 
1318
            # We refer to the new pack to delta data being output.
 
1319
            # A possible improvement would be to catch errors on short reads
 
1320
            # and only flush then.
 
1321
            self.new_pack.flush()
 
1322
            parents = []
 
1323
            for parent_key in parent_keys:
 
1324
                if parent_key[0] != key[0]:
 
1325
                    # Graph parents must match the fileid
 
1326
                    raise errors.BzrError('Mismatched key parent %r:%r' %
 
1327
                        (key, parent_keys))
 
1328
                parents.append(parent_key[1])
 
1329
            text_lines = osutils.split_lines(repo.texts.get_record_stream(
 
1330
                [key], 'unordered', True).next().get_bytes_as('fulltext'))
 
1331
            output_texts.add_lines(key, parent_keys, text_lines,
 
1332
                random_id=True, check_content=False)
 
1333
        # 5) check that nothing inserted has a reference outside the keyspace.
 
1334
        missing_text_keys = self.new_pack.text_index._external_references()
 
1335
        if missing_text_keys:
 
1336
            raise errors.BzrCheckError('Reference to missing compression parents %r'
 
1337
                % (missing_text_keys,))
 
1338
        self._log_copied_texts()
 
1339
 
 
1340
    def _use_pack(self, new_pack):
 
1341
        """Override _use_pack to check for reconcile having changed content."""
 
1342
        # XXX: we might be better checking this at the copy time.
 
1343
        original_inventory_keys = set()
 
1344
        inv_index = self._pack_collection.inventory_index.combined_index
 
1345
        for entry in inv_index.iter_all_entries():
 
1346
            original_inventory_keys.add(entry[1])
 
1347
        new_inventory_keys = set()
 
1348
        for entry in new_pack.inventory_index.iter_all_entries():
 
1349
            new_inventory_keys.add(entry[1])
 
1350
        if new_inventory_keys != original_inventory_keys:
 
1351
            self._data_changed = True
 
1352
        return new_pack.data_inserted() and self._data_changed
 
1353
 
 
1354
 
768
1355
class RepositoryPackCollection(object):
769
1356
    """Management of packs within a repository.
770
1357
 
771
1358
    :ivar _names: map of {pack_name: (index_size,)}
772
1359
    """
773
1360
 
774
 
    pack_factory = None
775
 
    resumed_pack_factory = None
776
 
    normal_packer_class = None
777
 
    optimising_packer_class = None
 
1361
    pack_factory = NewPack
 
1362
    resumed_pack_factory = ResumedPack
778
1363
 
779
1364
    def __init__(self, repo, transport, index_transport, upload_transport,
780
1365
                 pack_transport, index_builder_class, index_class,
831
1416
                set(all_combined).difference([combined_idx]))
832
1417
        # resumed packs
833
1418
        self._resumed_packs = []
834
 
        self.config_stack = config.LocationStack(self.transport.base)
835
1419
 
836
1420
    def __repr__(self):
837
1421
        return '%s(%r)' % (self.__class__.__name__, self.repo)
922
1506
            'containing %d revisions. Packing %d files into %d affecting %d'
923
1507
            ' revisions', self, total_packs, total_revisions, num_old_packs,
924
1508
            num_new_packs, num_revs_affected)
925
 
        result = self._execute_pack_operations(pack_operations, packer_class=self.normal_packer_class,
 
1509
        result = self._execute_pack_operations(pack_operations,
926
1510
                                      reload_func=self._restart_autopack)
927
1511
        mutter('Auto-packing repository %s completed', self)
928
1512
        return result
929
1513
 
930
 
    def _execute_pack_operations(self, pack_operations, packer_class,
931
 
            reload_func=None):
 
1514
    def _execute_pack_operations(self, pack_operations, _packer_class=Packer,
 
1515
                                 reload_func=None):
932
1516
        """Execute a series of pack operations.
933
1517
 
934
1518
        :param pack_operations: A list of [revision_count, packs_to_combine].
935
 
        :param packer_class: The class of packer to use
 
1519
        :param _packer_class: The class of packer to use (default: Packer).
936
1520
        :return: The new pack names.
937
1521
        """
938
1522
        for revision_count, packs in pack_operations:
939
1523
            # we may have no-ops from the setup logic
940
1524
            if len(packs) == 0:
941
1525
                continue
942
 
            packer = packer_class(self, packs, '.autopack',
 
1526
            packer = _packer_class(self, packs, '.autopack',
943
1527
                                   reload_func=reload_func)
944
1528
            try:
945
 
                result = packer.pack()
 
1529
                packer.pack()
946
1530
            except errors.RetryWithNewPacks:
947
1531
                # An exception is propagating out of this context, make sure
948
1532
                # this packer has cleaned up. Packer() doesn't set its new_pack
951
1535
                if packer.new_pack is not None:
952
1536
                    packer.new_pack.abort()
953
1537
                raise
954
 
            if result is None:
955
 
                return
956
1538
            for pack in packs:
957
1539
                self._remove_pack_from_memory(pack)
958
1540
        # record the newly available packs and stop advertising the old
992
1574
        mutter('Packing repository %s, which has %d pack files, '
993
1575
            'containing %d revisions with hint %r.', self, total_packs,
994
1576
            total_revisions, hint)
995
 
        while True:
996
 
            try:
997
 
                self._try_pack_operations(hint)
998
 
            except RetryPackOperations:
999
 
                continue
1000
 
            break
1001
 
 
1002
 
        if clean_obsolete_packs:
1003
 
            self._clear_obsolete_packs()
1004
 
 
1005
 
    def _try_pack_operations(self, hint):
1006
 
        """Calculate the pack operations based on the hint (if any), and
1007
 
        execute them.
1008
 
        """
1009
1577
        # determine which packs need changing
1010
1578
        pack_operations = [[0, []]]
1011
1579
        for pack in self.all_packs():
1014
1582
                # or this pack was included in the hint.
1015
1583
                pack_operations[-1][0] += pack.get_revision_count()
1016
1584
                pack_operations[-1][1].append(pack)
1017
 
        self._execute_pack_operations(pack_operations,
1018
 
            packer_class=self.optimising_packer_class,
1019
 
            reload_func=self._restart_pack_operations)
 
1585
        self._execute_pack_operations(pack_operations, OptimisingPacker)
 
1586
 
 
1587
        if clean_obsolete_packs:
 
1588
            self._clear_obsolete_packs()
1020
1589
 
1021
1590
    def plan_autopack_combinations(self, existing_packs, pack_distribution):
1022
1591
        """Plan a pack operation.
1032
1601
        pack_operations = [[0, []]]
1033
1602
        # plan out what packs to keep, and what to reorganise
1034
1603
        while len(existing_packs):
1035
 
            # take the largest pack, and if it's less than the head of the
 
1604
            # take the largest pack, and if its less than the head of the
1036
1605
            # distribution chart we will include its contents in the new pack
1037
 
            # for that position. If it's larger, we remove its size from the
 
1606
            # for that position. If its larger, we remove its size from the
1038
1607
            # distribution chart
1039
1608
            next_pack_rev_count, next_pack = existing_packs.pop(0)
1040
1609
            if next_pack_rev_count >= pack_distribution[0]:
1075
1644
 
1076
1645
        :return: True if the disk names had not been previously read.
1077
1646
        """
1078
 
        # NB: if you see an assertion error here, it's probably access against
 
1647
        # NB: if you see an assertion error here, its probably access against
1079
1648
        # an unlocked repo. Naughty.
1080
1649
        if not self.repo.is_locked():
1081
1650
            raise errors.ObjectNotLocked(self.repo)
1111
1680
            txt_index = self._make_index(name, '.tix')
1112
1681
            sig_index = self._make_index(name, '.six')
1113
1682
            if self.chk_index is not None:
1114
 
                chk_index = self._make_index(name, '.cix', is_chk=True)
 
1683
                chk_index = self._make_index(name, '.cix', unlimited_cache=True)
1115
1684
            else:
1116
1685
                chk_index = None
1117
1686
            result = ExistingPack(self._pack_transport, name, rev_index,
1137
1706
            sig_index = self._make_index(name, '.six', resume=True)
1138
1707
            if self.chk_index is not None:
1139
1708
                chk_index = self._make_index(name, '.cix', resume=True,
1140
 
                                             is_chk=True)
 
1709
                                             unlimited_cache=True)
1141
1710
            else:
1142
1711
                chk_index = None
1143
1712
            result = self.resumed_pack_factory(name, rev_index, inv_index,
1173
1742
        return self._index_class(self.transport, 'pack-names', None
1174
1743
                ).iter_all_entries()
1175
1744
 
1176
 
    def _make_index(self, name, suffix, resume=False, is_chk=False):
 
1745
    def _make_index(self, name, suffix, resume=False, unlimited_cache=False):
1177
1746
        size_offset = self._suffix_offsets[suffix]
1178
1747
        index_name = name + suffix
1179
1748
        if resume:
1182
1751
        else:
1183
1752
            transport = self._index_transport
1184
1753
            index_size = self._names[name][size_offset]
1185
 
        index = self._index_class(transport, index_name, index_size,
1186
 
                                  unlimited_cache=is_chk)
1187
 
        if is_chk and self._index_class is btree_index.BTreeGraphIndex: 
1188
 
            index._leaf_factory = btree_index._gcchk_factory
1189
 
        return index
 
1754
        return self._index_class(transport, index_name, index_size,
 
1755
                                 unlimited_cache=unlimited_cache)
1190
1756
 
1191
1757
    def _max_pack_count(self, total_revisions):
1192
1758
        """Return the maximum number of packs to use for total revisions.
1221
1787
        """
1222
1788
        for pack in packs:
1223
1789
            try:
1224
 
                pack.pack_transport.move(pack.file_name(),
 
1790
                pack.pack_transport.rename(pack.file_name(),
1225
1791
                    '../obsolete_packs/' + pack.file_name())
1226
1792
            except (errors.PathError, errors.TransportError), e:
1227
1793
                # TODO: Should these be warnings or mutters?
1235
1801
                suffixes.append('.cix')
1236
1802
            for suffix in suffixes:
1237
1803
                try:
1238
 
                    self._index_transport.move(pack.name + suffix,
 
1804
                    self._index_transport.rename(pack.name + suffix,
1239
1805
                        '../obsolete_packs/' + pack.name + suffix)
1240
1806
                except (errors.PathError, errors.TransportError), e:
1241
1807
                    mutter("couldn't rename obsolete index, skipping it:\n%s"
1378
1944
                    # disk index because the set values are the same, unless
1379
1945
                    # the only index shows up as deleted by the set difference
1380
1946
                    # - which it may. Until there is a specific test for this,
1381
 
                    # assume it's broken. RBC 20071017.
 
1947
                    # assume its broken. RBC 20071017.
1382
1948
                    self._remove_pack_from_memory(self.get_pack_by_name(name))
1383
1949
                    self._names[name] = sizes
1384
1950
                    self.get_pack_by_name(name)
1449
2015
        """
1450
2016
        # The ensure_loaded call is to handle the case where the first call
1451
2017
        # made involving the collection was to reload_pack_names, where we 
1452
 
        # don't have a view of disk contents. It's a bit of a bandaid, and
1453
 
        # causes two reads of pack-names, but it's a rare corner case not
1454
 
        # struck with regular push/pull etc.
 
2018
        # don't have a view of disk contents. Its a bit of a bandaid, and
 
2019
        # causes two reads of pack-names, but its a rare corner case not struck
 
2020
        # with regular push/pull etc.
1455
2021
        first_read = self.ensure_loaded()
1456
2022
        if first_read:
1457
2023
            return True
1476
2042
            raise
1477
2043
        raise errors.RetryAutopack(self.repo, False, sys.exc_info())
1478
2044
 
1479
 
    def _restart_pack_operations(self):
1480
 
        """Reload the pack names list, and restart the autopack code."""
1481
 
        if not self.reload_pack_names():
1482
 
            # Re-raise the original exception, because something went missing
1483
 
            # and a restart didn't find it
1484
 
            raise
1485
 
        raise RetryPackOperations(self.repo, False, sys.exc_info())
1486
 
 
1487
2045
    def _clear_obsolete_packs(self, preserve=None):
1488
2046
        """Delete everything from the obsolete-packs directory.
1489
2047
 
1637
2195
            self._resume_pack(token)
1638
2196
 
1639
2197
 
1640
 
class PackRepository(MetaDirVersionedFileRepository):
 
2198
class KnitPackRepository(KnitRepository):
1641
2199
    """Repository with knit objects stored inside pack containers.
1642
2200
 
1643
2201
    The layering for a KnitPackRepository is:
1646
2204
    ===================================================
1647
2205
    Tuple based apis below, string based, and key based apis above
1648
2206
    ---------------------------------------------------
1649
 
    VersionedFiles
 
2207
    KnitVersionedFiles
1650
2208
      Provides .texts, .revisions etc
1651
2209
      This adapts the N-tuple keys to physical knit records which only have a
1652
2210
      single string identifier (for historical reasons), which in older formats
1662
2220
 
1663
2221
    """
1664
2222
 
1665
 
    # These attributes are inherited from the Repository base class. Setting
1666
 
    # them to None ensures that if the constructor is changed to not initialize
1667
 
    # them, or a subclass fails to call the constructor, that an error will
1668
 
    # occur rather than the system working but generating incorrect data.
1669
 
    _commit_builder_class = None
1670
 
    _serializer = None
1671
 
 
1672
2223
    def __init__(self, _format, a_bzrdir, control_files, _commit_builder_class,
1673
2224
        _serializer):
1674
 
        MetaDirRepository.__init__(self, _format, a_bzrdir, control_files)
1675
 
        self._commit_builder_class = _commit_builder_class
1676
 
        self._serializer = _serializer
 
2225
        KnitRepository.__init__(self, _format, a_bzrdir, control_files,
 
2226
            _commit_builder_class, _serializer)
 
2227
        index_transport = self._transport.clone('indices')
 
2228
        self._pack_collection = RepositoryPackCollection(self, self._transport,
 
2229
            index_transport,
 
2230
            self._transport.clone('upload'),
 
2231
            self._transport.clone('packs'),
 
2232
            _format.index_builder_class,
 
2233
            _format.index_class,
 
2234
            use_chk_index=self._format.supports_chks,
 
2235
            )
 
2236
        self.inventories = KnitVersionedFiles(
 
2237
            _KnitGraphIndex(self._pack_collection.inventory_index.combined_index,
 
2238
                add_callback=self._pack_collection.inventory_index.add_callback,
 
2239
                deltas=True, parents=True, is_locked=self.is_locked),
 
2240
            data_access=self._pack_collection.inventory_index.data_access,
 
2241
            max_delta_chain=200)
 
2242
        self.revisions = KnitVersionedFiles(
 
2243
            _KnitGraphIndex(self._pack_collection.revision_index.combined_index,
 
2244
                add_callback=self._pack_collection.revision_index.add_callback,
 
2245
                deltas=False, parents=True, is_locked=self.is_locked,
 
2246
                track_external_parent_refs=True),
 
2247
            data_access=self._pack_collection.revision_index.data_access,
 
2248
            max_delta_chain=0)
 
2249
        self.signatures = KnitVersionedFiles(
 
2250
            _KnitGraphIndex(self._pack_collection.signature_index.combined_index,
 
2251
                add_callback=self._pack_collection.signature_index.add_callback,
 
2252
                deltas=False, parents=False, is_locked=self.is_locked),
 
2253
            data_access=self._pack_collection.signature_index.data_access,
 
2254
            max_delta_chain=0)
 
2255
        self.texts = KnitVersionedFiles(
 
2256
            _KnitGraphIndex(self._pack_collection.text_index.combined_index,
 
2257
                add_callback=self._pack_collection.text_index.add_callback,
 
2258
                deltas=True, parents=True, is_locked=self.is_locked),
 
2259
            data_access=self._pack_collection.text_index.data_access,
 
2260
            max_delta_chain=200)
 
2261
        if _format.supports_chks:
 
2262
            # No graph, no compression:- references from chks are between
 
2263
            # different objects not temporal versions of the same; and without
 
2264
            # some sort of temporal structure knit compression will just fail.
 
2265
            self.chk_bytes = KnitVersionedFiles(
 
2266
                _KnitGraphIndex(self._pack_collection.chk_index.combined_index,
 
2267
                    add_callback=self._pack_collection.chk_index.add_callback,
 
2268
                    deltas=False, parents=False, is_locked=self.is_locked),
 
2269
                data_access=self._pack_collection.chk_index.data_access,
 
2270
                max_delta_chain=0)
 
2271
        else:
 
2272
            self.chk_bytes = None
 
2273
        # True when the repository object is 'write locked' (as opposed to the
 
2274
        # physical lock only taken out around changes to the pack-names list.)
 
2275
        # Another way to represent this would be a decorator around the control
 
2276
        # files object that presents logical locks as physical ones - if this
 
2277
        # gets ugly consider that alternative design. RBC 20071011
 
2278
        self._write_lock_count = 0
 
2279
        self._transaction = None
 
2280
        # for tests
 
2281
        self._reconcile_does_inventory_gc = True
1677
2282
        self._reconcile_fixes_text_parents = True
1678
 
        if self._format.supports_external_lookups:
1679
 
            self._unstacked_provider = graph.CachingParentsProvider(
1680
 
                self._make_parents_provider_unstacked())
1681
 
        else:
1682
 
            self._unstacked_provider = graph.CachingParentsProvider(self)
1683
 
        self._unstacked_provider.disable_cache()
 
2283
        self._reconcile_backsup_inventory = False
1684
2284
 
1685
 
    @needs_read_lock
1686
 
    def _all_revision_ids(self):
1687
 
        """See Repository.all_revision_ids()."""
1688
 
        return [key[0] for key in self.revisions.keys()]
 
2285
    def _warn_if_deprecated(self, branch=None):
 
2286
        # This class isn't deprecated, but one sub-format is
 
2287
        if isinstance(self._format, RepositoryFormatKnitPack5RichRootBroken):
 
2288
            super(KnitPackRepository, self)._warn_if_deprecated(branch)
1689
2289
 
1690
2290
    def _abort_write_group(self):
1691
2291
        self.revisions._index._key_dependencies.clear()
1692
2292
        self._pack_collection._abort_write_group()
1693
2293
 
 
2294
    def _get_source(self, to_format):
 
2295
        if to_format.network_name() == self._format.network_name():
 
2296
            return KnitPackStreamSource(self, to_format)
 
2297
        return super(KnitPackRepository, self)._get_source(to_format)
 
2298
 
1694
2299
    def _make_parents_provider(self):
1695
 
        if not self._format.supports_external_lookups:
1696
 
            return self._unstacked_provider
1697
 
        return graph.StackedParentsProvider(_LazyListJoin(
1698
 
            [self._unstacked_provider], self._fallback_repositories))
 
2300
        return graph.CachingParentsProvider(self)
1699
2301
 
1700
2302
    def _refresh_data(self):
1701
2303
        if not self.is_locked():
1702
2304
            return
1703
2305
        self._pack_collection.reload_pack_names()
1704
 
        self._unstacked_provider.disable_cache()
1705
 
        self._unstacked_provider.enable_cache()
1706
2306
 
1707
2307
    def _start_write_group(self):
1708
2308
        self._pack_collection._start_write_group()
1710
2310
    def _commit_write_group(self):
1711
2311
        hint = self._pack_collection._commit_write_group()
1712
2312
        self.revisions._index._key_dependencies.clear()
1713
 
        # The commit may have added keys that were previously cached as
1714
 
        # missing, so reset the cache.
1715
 
        self._unstacked_provider.disable_cache()
1716
 
        self._unstacked_provider.enable_cache()
1717
2313
        return hint
1718
2314
 
1719
2315
    def suspend_write_group(self):
1760
2356
            if 'relock' in debug.debug_flags and self._prev_lock == 'w':
1761
2357
                note('%r was write locked again', self)
1762
2358
            self._prev_lock = 'w'
1763
 
            self._unstacked_provider.enable_cache()
1764
2359
            for repo in self._fallback_repositories:
1765
2360
                # Writes don't affect fallback repos
1766
2361
                repo.lock_read()
1781
2376
            if 'relock' in debug.debug_flags and self._prev_lock == 'r':
1782
2377
                note('%r was read locked again', self)
1783
2378
            self._prev_lock = 'r'
1784
 
            self._unstacked_provider.enable_cache()
1785
2379
            for repo in self._fallback_repositories:
1786
2380
                repo.lock_read()
1787
2381
            self._refresh_data()
1813
2407
        return reconciler
1814
2408
 
1815
2409
    def _reconcile_pack(self, collection, packs, extension, revs, pb):
1816
 
        raise NotImplementedError(self._reconcile_pack)
 
2410
        packer = ReconcilePacker(collection, packs, extension, revs)
 
2411
        return packer.pack(pb)
1817
2412
 
1818
2413
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1819
2414
    def unlock(self):
1820
2415
        if self._write_lock_count == 1 and self._write_group is not None:
1821
2416
            self.abort_write_group()
1822
 
            self._unstacked_provider.disable_cache()
1823
2417
            self._transaction = None
1824
2418
            self._write_lock_count = 0
1825
2419
            raise errors.BzrError(
1835
2429
            self.control_files.unlock()
1836
2430
 
1837
2431
        if not self.is_locked():
1838
 
            self._unstacked_provider.disable_cache()
1839
2432
            for repo in self._fallback_repositories:
1840
2433
                repo.unlock()
1841
2434
 
1842
2435
 
1843
 
class RepositoryFormatPack(MetaDirVersionedFileRepositoryFormat):
 
2436
class KnitPackStreamSource(StreamSource):
 
2437
    """A StreamSource used to transfer data between same-format KnitPack repos.
 
2438
 
 
2439
    This source assumes:
 
2440
        1) Same serialization format for all objects
 
2441
        2) Same root information
 
2442
        3) XML format inventories
 
2443
        4) Atomic inserts (so we can stream inventory texts before text
 
2444
           content)
 
2445
        5) No chk_bytes
 
2446
    """
 
2447
 
 
2448
    def __init__(self, from_repository, to_format):
 
2449
        super(KnitPackStreamSource, self).__init__(from_repository, to_format)
 
2450
        self._text_keys = None
 
2451
        self._text_fetch_order = 'unordered'
 
2452
 
 
2453
    def _get_filtered_inv_stream(self, revision_ids):
 
2454
        from_repo = self.from_repository
 
2455
        parent_ids = from_repo._find_parent_ids_of_revisions(revision_ids)
 
2456
        parent_keys = [(p,) for p in parent_ids]
 
2457
        find_text_keys = from_repo._find_text_key_references_from_xml_inventory_lines
 
2458
        parent_text_keys = set(find_text_keys(
 
2459
            from_repo._inventory_xml_lines_for_keys(parent_keys)))
 
2460
        content_text_keys = set()
 
2461
        knit = KnitVersionedFiles(None, None)
 
2462
        factory = KnitPlainFactory()
 
2463
        def find_text_keys_from_content(record):
 
2464
            if record.storage_kind not in ('knit-delta-gz', 'knit-ft-gz'):
 
2465
                raise ValueError("Unknown content storage kind for"
 
2466
                    " inventory text: %s" % (record.storage_kind,))
 
2467
            # It's a knit record, it has a _raw_record field (even if it was
 
2468
            # reconstituted from a network stream).
 
2469
            raw_data = record._raw_record
 
2470
            # read the entire thing
 
2471
            revision_id = record.key[-1]
 
2472
            content, _ = knit._parse_record(revision_id, raw_data)
 
2473
            if record.storage_kind == 'knit-delta-gz':
 
2474
                line_iterator = factory.get_linedelta_content(content)
 
2475
            elif record.storage_kind == 'knit-ft-gz':
 
2476
                line_iterator = factory.get_fulltext_content(content)
 
2477
            content_text_keys.update(find_text_keys(
 
2478
                [(line, revision_id) for line in line_iterator]))
 
2479
        revision_keys = [(r,) for r in revision_ids]
 
2480
        def _filtered_inv_stream():
 
2481
            source_vf = from_repo.inventories
 
2482
            stream = source_vf.get_record_stream(revision_keys,
 
2483
                                                 'unordered', False)
 
2484
            for record in stream:
 
2485
                if record.storage_kind == 'absent':
 
2486
                    raise errors.NoSuchRevision(from_repo, record.key)
 
2487
                find_text_keys_from_content(record)
 
2488
                yield record
 
2489
            self._text_keys = content_text_keys - parent_text_keys
 
2490
        return ('inventories', _filtered_inv_stream())
 
2491
 
 
2492
    def _get_text_stream(self):
 
2493
        # Note: We know we don't have to handle adding root keys, because both
 
2494
        # the source and target are the identical network name.
 
2495
        text_stream = self.from_repository.texts.get_record_stream(
 
2496
                        self._text_keys, self._text_fetch_order, False)
 
2497
        return ('texts', text_stream)
 
2498
 
 
2499
    def get_stream(self, search):
 
2500
        revision_ids = search.get_keys()
 
2501
        for stream_info in self._fetch_revision_texts(revision_ids):
 
2502
            yield stream_info
 
2503
        self._revision_keys = [(rev_id,) for rev_id in revision_ids]
 
2504
        yield self._get_filtered_inv_stream(revision_ids)
 
2505
        yield self._get_text_stream()
 
2506
 
 
2507
 
 
2508
 
 
2509
class RepositoryFormatPack(MetaDirRepositoryFormat):
1844
2510
    """Format logic for pack structured repositories.
1845
2511
 
1846
2512
    This repository format has:
1876
2542
    index_class = None
1877
2543
    _fetch_uses_deltas = True
1878
2544
    fast_deltas = False
1879
 
    supports_funky_characters = True
1880
 
    revision_graph_can_have_wrong_parents = True
1881
2545
 
1882
2546
    def initialize(self, a_bzrdir, shared=False):
1883
2547
        """Create a pack based repository.
1920
2584
                              _serializer=self._serializer)
1921
2585
 
1922
2586
 
1923
 
class RetryPackOperations(errors.RetryWithNewPacks):
1924
 
    """Raised when we are packing and we find a missing file.
1925
 
 
1926
 
    Meant as a signaling exception, to tell the RepositoryPackCollection.pack
1927
 
    code it should try again.
1928
 
    """
1929
 
 
1930
 
    internal_error = True
1931
 
 
1932
 
    _fmt = ("Pack files have changed, reload and try pack again."
1933
 
            " context: %(context)s %(orig_error)s")
1934
 
 
1935
 
 
1936
 
class _DirectPackAccess(object):
1937
 
    """Access to data in one or more packs with less translation."""
1938
 
 
1939
 
    def __init__(self, index_to_packs, reload_func=None, flush_func=None):
1940
 
        """Create a _DirectPackAccess object.
1941
 
 
1942
 
        :param index_to_packs: A dict mapping index objects to the transport
1943
 
            and file names for obtaining data.
1944
 
        :param reload_func: A function to call if we determine that the pack
1945
 
            files have moved and we need to reload our caches. See
1946
 
            bzrlib.repo_fmt.pack_repo.AggregateIndex for more details.
1947
 
        """
1948
 
        self._container_writer = None
1949
 
        self._write_index = None
1950
 
        self._indices = index_to_packs
1951
 
        self._reload_func = reload_func
1952
 
        self._flush_func = flush_func
1953
 
 
1954
 
    def add_raw_records(self, key_sizes, raw_data):
1955
 
        """Add raw knit bytes to a storage area.
1956
 
 
1957
 
        The data is spooled to the container writer in one bytes-record per
1958
 
        raw data item.
1959
 
 
1960
 
        :param sizes: An iterable of tuples containing the key and size of each
1961
 
            raw data segment.
1962
 
        :param raw_data: A bytestring containing the data.
1963
 
        :return: A list of memos to retrieve the record later. Each memo is an
1964
 
            opaque index memo. For _DirectPackAccess the memo is (index, pos,
1965
 
            length), where the index field is the write_index object supplied
1966
 
            to the PackAccess object.
1967
 
        """
1968
 
        if type(raw_data) is not str:
1969
 
            raise AssertionError(
1970
 
                'data must be plain bytes was %s' % type(raw_data))
1971
 
        result = []
1972
 
        offset = 0
1973
 
        for key, size in key_sizes:
1974
 
            p_offset, p_length = self._container_writer.add_bytes_record(
1975
 
                raw_data[offset:offset+size], [])
1976
 
            offset += size
1977
 
            result.append((self._write_index, p_offset, p_length))
1978
 
        return result
1979
 
 
1980
 
    def flush(self):
1981
 
        """Flush pending writes on this access object.
1982
 
 
1983
 
        This will flush any buffered writes to a NewPack.
1984
 
        """
1985
 
        if self._flush_func is not None:
1986
 
            self._flush_func()
1987
 
 
1988
 
    def get_raw_records(self, memos_for_retrieval):
1989
 
        """Get the raw bytes for a records.
1990
 
 
1991
 
        :param memos_for_retrieval: An iterable containing the (index, pos,
1992
 
            length) memo for retrieving the bytes. The Pack access method
1993
 
            looks up the pack to use for a given record in its index_to_pack
1994
 
            map.
1995
 
        :return: An iterator over the bytes of the records.
1996
 
        """
1997
 
        # first pass, group into same-index requests
1998
 
        request_lists = []
1999
 
        current_index = None
2000
 
        for (index, offset, length) in memos_for_retrieval:
2001
 
            if current_index == index:
2002
 
                current_list.append((offset, length))
2003
 
            else:
2004
 
                if current_index is not None:
2005
 
                    request_lists.append((current_index, current_list))
2006
 
                current_index = index
2007
 
                current_list = [(offset, length)]
2008
 
        # handle the last entry
2009
 
        if current_index is not None:
2010
 
            request_lists.append((current_index, current_list))
2011
 
        for index, offsets in request_lists:
2012
 
            try:
2013
 
                transport, path = self._indices[index]
2014
 
            except KeyError:
2015
 
                # A KeyError here indicates that someone has triggered an index
2016
 
                # reload, and this index has gone missing, we need to start
2017
 
                # over.
2018
 
                if self._reload_func is None:
2019
 
                    # If we don't have a _reload_func there is nothing that can
2020
 
                    # be done
2021
 
                    raise
2022
 
                raise errors.RetryWithNewPacks(index,
2023
 
                                               reload_occurred=True,
2024
 
                                               exc_info=sys.exc_info())
2025
 
            try:
2026
 
                reader = pack.make_readv_reader(transport, path, offsets)
2027
 
                for names, read_func in reader.iter_records():
2028
 
                    yield read_func(None)
2029
 
            except errors.NoSuchFile:
2030
 
                # A NoSuchFile error indicates that a pack file has gone
2031
 
                # missing on disk, we need to trigger a reload, and start over.
2032
 
                if self._reload_func is None:
2033
 
                    raise
2034
 
                raise errors.RetryWithNewPacks(transport.abspath(path),
2035
 
                                               reload_occurred=False,
2036
 
                                               exc_info=sys.exc_info())
2037
 
 
2038
 
    def set_writer(self, writer, index, transport_packname):
2039
 
        """Set a writer to use for adding data."""
2040
 
        if index is not None:
2041
 
            self._indices[index] = transport_packname
2042
 
        self._container_writer = writer
2043
 
        self._write_index = index
2044
 
 
2045
 
    def reload_or_raise(self, retry_exc):
2046
 
        """Try calling the reload function, or re-raise the original exception.
2047
 
 
2048
 
        This should be called after _DirectPackAccess raises a
2049
 
        RetryWithNewPacks exception. This function will handle the common logic
2050
 
        of determining when the error is fatal versus being temporary.
2051
 
        It will also make sure that the original exception is raised, rather
2052
 
        than the RetryWithNewPacks exception.
2053
 
 
2054
 
        If this function returns, then the calling function should retry
2055
 
        whatever operation was being performed. Otherwise an exception will
2056
 
        be raised.
2057
 
 
2058
 
        :param retry_exc: A RetryWithNewPacks exception.
2059
 
        """
2060
 
        is_error = False
2061
 
        if self._reload_func is None:
2062
 
            is_error = True
2063
 
        elif not self._reload_func():
2064
 
            # The reload claimed that nothing changed
2065
 
            if not retry_exc.reload_occurred:
2066
 
                # If there wasn't an earlier reload, then we really were
2067
 
                # expecting to find changes. We didn't find them, so this is a
2068
 
                # hard error
2069
 
                is_error = True
2070
 
        if is_error:
2071
 
            exc_class, exc_value, exc_traceback = retry_exc.exc_info
2072
 
            raise exc_class, exc_value, exc_traceback
2073
 
 
2074
 
 
 
2587
class RepositoryFormatKnitPack1(RepositoryFormatPack):
 
2588
    """A no-subtrees parameterized Pack repository.
 
2589
 
 
2590
    This format was introduced in 0.92.
 
2591
    """
 
2592
 
 
2593
    repository_class = KnitPackRepository
 
2594
    _commit_builder_class = PackCommitBuilder
 
2595
    @property
 
2596
    def _serializer(self):
 
2597
        return xml5.serializer_v5
 
2598
    # What index classes to use
 
2599
    index_builder_class = InMemoryGraphIndex
 
2600
    index_class = GraphIndex
 
2601
 
 
2602
    def _get_matching_bzrdir(self):
 
2603
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
 
2604
 
 
2605
    def _ignore_setting_bzrdir(self, format):
 
2606
        pass
 
2607
 
 
2608
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2609
 
 
2610
    def get_format_string(self):
 
2611
        """See RepositoryFormat.get_format_string()."""
 
2612
        return "Bazaar pack repository format 1 (needs bzr 0.92)\n"
 
2613
 
 
2614
    def get_format_description(self):
 
2615
        """See RepositoryFormat.get_format_description()."""
 
2616
        return "Packs containing knits without subtree support"
 
2617
 
 
2618
 
 
2619
class RepositoryFormatKnitPack3(RepositoryFormatPack):
 
2620
    """A subtrees parameterized Pack repository.
 
2621
 
 
2622
    This repository format uses the xml7 serializer to get:
 
2623
     - support for recording full info about the tree root
 
2624
     - support for recording tree-references
 
2625
 
 
2626
    This format was introduced in 0.92.
 
2627
    """
 
2628
 
 
2629
    repository_class = KnitPackRepository
 
2630
    _commit_builder_class = PackRootCommitBuilder
 
2631
    rich_root_data = True
 
2632
    experimental = True
 
2633
    supports_tree_reference = True
 
2634
    @property
 
2635
    def _serializer(self):
 
2636
        return xml7.serializer_v7
 
2637
    # What index classes to use
 
2638
    index_builder_class = InMemoryGraphIndex
 
2639
    index_class = GraphIndex
 
2640
 
 
2641
    def _get_matching_bzrdir(self):
 
2642
        return bzrdir.format_registry.make_bzrdir(
 
2643
            'pack-0.92-subtree')
 
2644
 
 
2645
    def _ignore_setting_bzrdir(self, format):
 
2646
        pass
 
2647
 
 
2648
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2649
 
 
2650
    def get_format_string(self):
 
2651
        """See RepositoryFormat.get_format_string()."""
 
2652
        return "Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n"
 
2653
 
 
2654
    def get_format_description(self):
 
2655
        """See RepositoryFormat.get_format_description()."""
 
2656
        return "Packs containing knits with subtree support\n"
 
2657
 
 
2658
 
 
2659
class RepositoryFormatKnitPack4(RepositoryFormatPack):
 
2660
    """A rich-root, no subtrees parameterized Pack repository.
 
2661
 
 
2662
    This repository format uses the xml6 serializer to get:
 
2663
     - support for recording full info about the tree root
 
2664
 
 
2665
    This format was introduced in 1.0.
 
2666
    """
 
2667
 
 
2668
    repository_class = KnitPackRepository
 
2669
    _commit_builder_class = PackRootCommitBuilder
 
2670
    rich_root_data = True
 
2671
    supports_tree_reference = False
 
2672
    @property
 
2673
    def _serializer(self):
 
2674
        return xml6.serializer_v6
 
2675
    # What index classes to use
 
2676
    index_builder_class = InMemoryGraphIndex
 
2677
    index_class = GraphIndex
 
2678
 
 
2679
    def _get_matching_bzrdir(self):
 
2680
        return bzrdir.format_registry.make_bzrdir(
 
2681
            'rich-root-pack')
 
2682
 
 
2683
    def _ignore_setting_bzrdir(self, format):
 
2684
        pass
 
2685
 
 
2686
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2687
 
 
2688
    def get_format_string(self):
 
2689
        """See RepositoryFormat.get_format_string()."""
 
2690
        return ("Bazaar pack repository format 1 with rich root"
 
2691
                " (needs bzr 1.0)\n")
 
2692
 
 
2693
    def get_format_description(self):
 
2694
        """See RepositoryFormat.get_format_description()."""
 
2695
        return "Packs containing knits with rich root support\n"
 
2696
 
 
2697
 
 
2698
class RepositoryFormatKnitPack5(RepositoryFormatPack):
 
2699
    """Repository that supports external references to allow stacking.
 
2700
 
 
2701
    New in release 1.6.
 
2702
 
 
2703
    Supports external lookups, which results in non-truncated ghosts after
 
2704
    reconcile compared to pack-0.92 formats.
 
2705
    """
 
2706
 
 
2707
    repository_class = KnitPackRepository
 
2708
    _commit_builder_class = PackCommitBuilder
 
2709
    supports_external_lookups = True
 
2710
    # What index classes to use
 
2711
    index_builder_class = InMemoryGraphIndex
 
2712
    index_class = GraphIndex
 
2713
 
 
2714
    @property
 
2715
    def _serializer(self):
 
2716
        return xml5.serializer_v5
 
2717
 
 
2718
    def _get_matching_bzrdir(self):
 
2719
        return bzrdir.format_registry.make_bzrdir('1.6')
 
2720
 
 
2721
    def _ignore_setting_bzrdir(self, format):
 
2722
        pass
 
2723
 
 
2724
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2725
 
 
2726
    def get_format_string(self):
 
2727
        """See RepositoryFormat.get_format_string()."""
 
2728
        return "Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n"
 
2729
 
 
2730
    def get_format_description(self):
 
2731
        """See RepositoryFormat.get_format_description()."""
 
2732
        return "Packs 5 (adds stacking support, requires bzr 1.6)"
 
2733
 
 
2734
 
 
2735
class RepositoryFormatKnitPack5RichRoot(RepositoryFormatPack):
 
2736
    """A repository with rich roots and stacking.
 
2737
 
 
2738
    New in release 1.6.1.
 
2739
 
 
2740
    Supports stacking on other repositories, allowing data to be accessed
 
2741
    without being stored locally.
 
2742
    """
 
2743
 
 
2744
    repository_class = KnitPackRepository
 
2745
    _commit_builder_class = PackRootCommitBuilder
 
2746
    rich_root_data = True
 
2747
    supports_tree_reference = False # no subtrees
 
2748
    supports_external_lookups = True
 
2749
    # What index classes to use
 
2750
    index_builder_class = InMemoryGraphIndex
 
2751
    index_class = GraphIndex
 
2752
 
 
2753
    @property
 
2754
    def _serializer(self):
 
2755
        return xml6.serializer_v6
 
2756
 
 
2757
    def _get_matching_bzrdir(self):
 
2758
        return bzrdir.format_registry.make_bzrdir(
 
2759
            '1.6.1-rich-root')
 
2760
 
 
2761
    def _ignore_setting_bzrdir(self, format):
 
2762
        pass
 
2763
 
 
2764
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2765
 
 
2766
    def get_format_string(self):
 
2767
        """See RepositoryFormat.get_format_string()."""
 
2768
        return "Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n"
 
2769
 
 
2770
    def get_format_description(self):
 
2771
        return "Packs 5 rich-root (adds stacking support, requires bzr 1.6.1)"
 
2772
 
 
2773
 
 
2774
class RepositoryFormatKnitPack5RichRootBroken(RepositoryFormatPack):
 
2775
    """A repository with rich roots and external references.
 
2776
 
 
2777
    New in release 1.6.
 
2778
 
 
2779
    Supports external lookups, which results in non-truncated ghosts after
 
2780
    reconcile compared to pack-0.92 formats.
 
2781
 
 
2782
    This format was deprecated because the serializer it uses accidentally
 
2783
    supported subtrees, when the format was not intended to. This meant that
 
2784
    someone could accidentally fetch from an incorrect repository.
 
2785
    """
 
2786
 
 
2787
    repository_class = KnitPackRepository
 
2788
    _commit_builder_class = PackRootCommitBuilder
 
2789
    rich_root_data = True
 
2790
    supports_tree_reference = False # no subtrees
 
2791
 
 
2792
    supports_external_lookups = True
 
2793
    # What index classes to use
 
2794
    index_builder_class = InMemoryGraphIndex
 
2795
    index_class = GraphIndex
 
2796
 
 
2797
    @property
 
2798
    def _serializer(self):
 
2799
        return xml7.serializer_v7
 
2800
 
 
2801
    def _get_matching_bzrdir(self):
 
2802
        matching = bzrdir.format_registry.make_bzrdir(
 
2803
            '1.6.1-rich-root')
 
2804
        matching.repository_format = self
 
2805
        return matching
 
2806
 
 
2807
    def _ignore_setting_bzrdir(self, format):
 
2808
        pass
 
2809
 
 
2810
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2811
 
 
2812
    def get_format_string(self):
 
2813
        """See RepositoryFormat.get_format_string()."""
 
2814
        return "Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n"
 
2815
 
 
2816
    def get_format_description(self):
 
2817
        return ("Packs 5 rich-root (adds stacking support, requires bzr 1.6)"
 
2818
                " (deprecated)")
 
2819
 
 
2820
 
 
2821
class RepositoryFormatKnitPack6(RepositoryFormatPack):
 
2822
    """A repository with stacking and btree indexes,
 
2823
    without rich roots or subtrees.
 
2824
 
 
2825
    This is equivalent to pack-1.6 with B+Tree indices.
 
2826
    """
 
2827
 
 
2828
    repository_class = KnitPackRepository
 
2829
    _commit_builder_class = PackCommitBuilder
 
2830
    supports_external_lookups = True
 
2831
    # What index classes to use
 
2832
    index_builder_class = BTreeBuilder
 
2833
    index_class = BTreeGraphIndex
 
2834
 
 
2835
    @property
 
2836
    def _serializer(self):
 
2837
        return xml5.serializer_v5
 
2838
 
 
2839
    def _get_matching_bzrdir(self):
 
2840
        return bzrdir.format_registry.make_bzrdir('1.9')
 
2841
 
 
2842
    def _ignore_setting_bzrdir(self, format):
 
2843
        pass
 
2844
 
 
2845
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2846
 
 
2847
    def get_format_string(self):
 
2848
        """See RepositoryFormat.get_format_string()."""
 
2849
        return "Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n"
 
2850
 
 
2851
    def get_format_description(self):
 
2852
        """See RepositoryFormat.get_format_description()."""
 
2853
        return "Packs 6 (uses btree indexes, requires bzr 1.9)"
 
2854
 
 
2855
 
 
2856
class RepositoryFormatKnitPack6RichRoot(RepositoryFormatPack):
 
2857
    """A repository with rich roots, no subtrees, stacking and btree indexes.
 
2858
 
 
2859
    1.6-rich-root with B+Tree indices.
 
2860
    """
 
2861
 
 
2862
    repository_class = KnitPackRepository
 
2863
    _commit_builder_class = PackRootCommitBuilder
 
2864
    rich_root_data = True
 
2865
    supports_tree_reference = False # no subtrees
 
2866
    supports_external_lookups = True
 
2867
    # What index classes to use
 
2868
    index_builder_class = BTreeBuilder
 
2869
    index_class = BTreeGraphIndex
 
2870
 
 
2871
    @property
 
2872
    def _serializer(self):
 
2873
        return xml6.serializer_v6
 
2874
 
 
2875
    def _get_matching_bzrdir(self):
 
2876
        return bzrdir.format_registry.make_bzrdir(
 
2877
            '1.9-rich-root')
 
2878
 
 
2879
    def _ignore_setting_bzrdir(self, format):
 
2880
        pass
 
2881
 
 
2882
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2883
 
 
2884
    def get_format_string(self):
 
2885
        """See RepositoryFormat.get_format_string()."""
 
2886
        return "Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n"
 
2887
 
 
2888
    def get_format_description(self):
 
2889
        return "Packs 6 rich-root (uses btree indexes, requires bzr 1.9)"
 
2890
 
 
2891
 
 
2892
class RepositoryFormatPackDevelopment2Subtree(RepositoryFormatPack):
 
2893
    """A subtrees development repository.
 
2894
 
 
2895
    This format should be retained until the second release after bzr 1.7.
 
2896
 
 
2897
    1.6.1-subtree[as it might have been] with B+Tree indices.
 
2898
 
 
2899
    This is [now] retained until we have a CHK based subtree format in
 
2900
    development.
 
2901
    """
 
2902
 
 
2903
    repository_class = KnitPackRepository
 
2904
    _commit_builder_class = PackRootCommitBuilder
 
2905
    rich_root_data = True
 
2906
    experimental = True
 
2907
    supports_tree_reference = True
 
2908
    supports_external_lookups = True
 
2909
    # What index classes to use
 
2910
    index_builder_class = BTreeBuilder
 
2911
    index_class = BTreeGraphIndex
 
2912
 
 
2913
    @property
 
2914
    def _serializer(self):
 
2915
        return xml7.serializer_v7
 
2916
 
 
2917
    def _get_matching_bzrdir(self):
 
2918
        return bzrdir.format_registry.make_bzrdir(
 
2919
            'development-subtree')
 
2920
 
 
2921
    def _ignore_setting_bzrdir(self, format):
 
2922
        pass
 
2923
 
 
2924
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2925
 
 
2926
    def get_format_string(self):
 
2927
        """See RepositoryFormat.get_format_string()."""
 
2928
        return ("Bazaar development format 2 with subtree support "
 
2929
            "(needs bzr.dev from before 1.8)\n")
 
2930
 
 
2931
    def get_format_description(self):
 
2932
        """See RepositoryFormat.get_format_description()."""
 
2933
        return ("Development repository format, currently the same as "
 
2934
            "1.6.1-subtree with B+Tree indices.\n")
2075
2935