~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: Jelmer Vernooij
  • Date: 2011-10-14 13:56:45 UTC
  • mfrom: (6215 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6216.
  • Revision ID: jelmer@samba.org-20111014135645-phc3q3y21k2ks0s2
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

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