~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: Jelmer Vernooij
  • Date: 2009-04-10 15:58:09 UTC
  • mto: This revision was merged to the branch mainline in revision 4284.
  • Revision ID: jelmer@samba.org-20090410155809-kdibzcjvp7pdb83f
Fix missing import.

Show diffs side-by-side

added added

removed removed

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