~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: Jelmer Vernooij
  • Date: 2010-01-20 22:32:07 UTC
  • mto: This revision was merged to the branch mainline in revision 4977.
  • Revision ID: jelmer@samba.org-20100120223207-pfz89u161ahyzvnt
Add FileTimestampUnavailable exception.

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