~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-04-27 22:07:03 UTC
  • mfrom: (4301.2.5 bzr.ab.integration)
  • Revision ID: pqm@pqm.ubuntu.com-20090427220703-oy9b0mxobrksvuyq
(gbache) Handle symlinks better in bzr add

Show diffs side-by-side

added added

removed removed

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