/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: Robert Collins
  • Date: 2010-05-05 00:05:29 UTC
  • mto: This revision was merged to the branch mainline in revision 5206.
  • Revision ID: robertc@robertcollins.net-20100505000529-ltmllyms5watqj5u
Make 'pydoc bzrlib.tests.build_tree_shape' useful.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 Canonical Ltd
 
1
# Copyright (C) 2007-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
17
import re
20
18
import sys
21
19
 
22
 
from ..lazy_import import lazy_import
 
20
from bzrlib.lazy_import import lazy_import
23
21
lazy_import(globals(), """
 
22
from itertools import izip
24
23
import time
25
24
 
26
 
from breezy import (
 
25
from bzrlib import (
 
26
    chk_map,
27
27
    cleanup,
28
 
    config,
29
28
    debug,
30
29
    graph,
31
30
    osutils,
 
31
    pack,
32
32
    transactions,
33
33
    ui,
34
 
    )
35
 
from breezy.bzr import (
36
 
    pack,
37
 
    )
38
 
from breezy.bzr.index import (
 
34
    xml5,
 
35
    xml6,
 
36
    xml7,
 
37
    )
 
38
from bzrlib.index import (
39
39
    CombinedGraphIndex,
40
 
    )
 
40
    GraphIndexPrefixAdapter,
 
41
    )
 
42
from bzrlib.knit import (
 
43
    KnitPlainFactory,
 
44
    KnitVersionedFiles,
 
45
    _KnitGraphIndex,
 
46
    _DirectPackAccess,
 
47
    )
 
48
from bzrlib import tsort
41
49
""")
42
 
from .. import (
 
50
from bzrlib import (
 
51
    bzrdir,
43
52
    errors,
44
53
    lockable_files,
45
54
    lockdir,
46
 
    )
47
 
from ..bzr import (
48
 
    btree_index,
 
55
    revision as _mod_revision,
49
56
    )
50
57
 
51
 
from ..decorators import (
52
 
    only_raises,
53
 
    )
54
 
from ..lock import LogicalLockResult
55
 
from ..repository import (
56
 
    _LazyListJoin,
57
 
    RepositoryWriteLockResult,
58
 
    )
59
 
from ..bzr.repository import (
60
 
    MetaDirRepository,
61
 
    RepositoryFormatMetaDir,
62
 
    )
63
 
from ..sixish import (
64
 
    reraise,
65
 
    viewitems,
66
 
    )
67
 
from ..bzr.vf_repository import (
68
 
    MetaDirVersionedFileRepository,
69
 
    MetaDirVersionedFileRepositoryFormat,
70
 
    VersionedFileCommitBuilder,
71
 
    )
72
 
from ..trace import (
 
58
from bzrlib.decorators import needs_write_lock, only_raises
 
59
from bzrlib.btree_index import (
 
60
    BTreeGraphIndex,
 
61
    BTreeBuilder,
 
62
    )
 
63
from bzrlib.index import (
 
64
    GraphIndex,
 
65
    InMemoryGraphIndex,
 
66
    )
 
67
from bzrlib.repofmt.knitrepo import KnitRepository
 
68
from bzrlib.repository import (
 
69
    CommitBuilder,
 
70
    MetaDirRepositoryFormat,
 
71
    RepositoryFormat,
 
72
    RootCommitBuilder,
 
73
    StreamSource,
 
74
    )
 
75
from bzrlib.trace import (
73
76
    mutter,
74
77
    note,
75
78
    warning,
76
79
    )
77
80
 
78
81
 
79
 
class PackCommitBuilder(VersionedFileCommitBuilder):
80
 
    """Subclass of VersionedFileCommitBuilder to add texts with pack semantics.
81
 
 
82
 
    Specifically this uses one knit object rather than one knit object per
83
 
    added text, reducing memory and object pressure.
84
 
    """
85
 
 
86
 
    def __init__(self, repository, parents, config, timestamp=None,
87
 
                 timezone=None, committer=None, revprops=None,
88
 
                 revision_id=None, lossy=False):
89
 
        VersionedFileCommitBuilder.__init__(self, repository, parents, config,
90
 
                                            timestamp=timestamp, timezone=timezone, committer=committer,
91
 
                                            revprops=revprops, revision_id=revision_id, lossy=lossy)
92
 
        self._file_graph = graph.Graph(
93
 
            repository._pack_collection.text_index.combined_index)
94
 
 
95
 
    def _heads(self, file_id, revision_ids):
96
 
        keys = [(file_id, revision_id) for revision_id in revision_ids]
97
 
        return {key[1] for key in self._file_graph.heads(keys)}
 
82
class PackCommitBuilder(CommitBuilder):
 
83
    """A subclass of CommitBuilder to add texts with pack semantics.
 
84
 
 
85
    Specifically this uses one knit object rather than one knit object per
 
86
    added text, reducing memory and object pressure.
 
87
    """
 
88
 
 
89
    def __init__(self, repository, parents, config, timestamp=None,
 
90
                 timezone=None, committer=None, revprops=None,
 
91
                 revision_id=None):
 
92
        CommitBuilder.__init__(self, repository, parents, config,
 
93
            timestamp=timestamp, timezone=timezone, committer=committer,
 
94
            revprops=revprops, revision_id=revision_id)
 
95
        self._file_graph = graph.Graph(
 
96
            repository._pack_collection.text_index.combined_index)
 
97
 
 
98
    def _heads(self, file_id, revision_ids):
 
99
        keys = [(file_id, revision_id) for revision_id in revision_ids]
 
100
        return set([key[1] for key in self._file_graph.heads(keys)])
 
101
 
 
102
 
 
103
class PackRootCommitBuilder(RootCommitBuilder):
 
104
    """A subclass of RootCommitBuilder to add texts with pack semantics.
 
105
 
 
106
    Specifically this uses one knit object rather than one knit object per
 
107
    added text, reducing memory and object pressure.
 
108
    """
 
109
 
 
110
    def __init__(self, repository, parents, config, timestamp=None,
 
111
                 timezone=None, committer=None, revprops=None,
 
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)
 
116
        self._file_graph = graph.Graph(
 
117
            repository._pack_collection.text_index.combined_index)
 
118
 
 
119
    def _heads(self, file_id, revision_ids):
 
120
        keys = [(file_id, revision_id) for revision_id in revision_ids]
 
121
        return set([key[1] for key in self._file_graph.heads(keys)])
98
122
 
99
123
 
100
124
class Pack(object):
115
139
        }
116
140
 
117
141
    def __init__(self, revision_index, inventory_index, text_index,
118
 
                 signature_index, chk_index=None):
 
142
        signature_index, chk_index=None):
119
143
        """Create a pack instance.
120
144
 
121
145
        :param revision_index: A GraphIndex for determining what revisions are
151
175
        """
152
176
        missing_items = {}
153
177
        for (index_name, external_refs, index) in [
154
 
                ('texts',
155
 
                    self._get_external_refs(self.text_index),
156
 
                    self._pack_collection.text_index.combined_index),
157
 
                ('inventories',
158
 
                    self._get_external_refs(self.inventory_index),
159
 
                    self._pack_collection.inventory_index.combined_index),
160
 
                ]:
 
178
            ('texts',
 
179
                self._get_external_refs(self.text_index),
 
180
                self._pack_collection.text_index.combined_index),
 
181
            ('inventories',
 
182
                self._get_external_refs(self.inventory_index),
 
183
                self._pack_collection.inventory_index.combined_index),
 
184
            ]:
161
185
            missing = external_refs.difference(
162
186
                k for (idx, k, v, r) in
163
187
                index.iter_entries(external_refs))
205
229
        unlimited_cache = False
206
230
        if index_type == 'chk':
207
231
            unlimited_cache = True
208
 
        index = self.index_class(self.index_transport,
209
 
                                 self.index_name(index_type, self.name),
210
 
                                 self.index_sizes[self.index_offset(
211
 
                                     index_type)],
212
 
                                 unlimited_cache=unlimited_cache)
213
 
        if index_type == 'chk':
214
 
            index._leaf_factory = btree_index._gcchk_factory
215
 
        setattr(self, index_type + '_index', index)
216
 
 
217
 
    def __lt__(self, other):
218
 
        if not isinstance(other, Pack):
219
 
            raise TypeError(other)
220
 
        return (id(self) < id(other))
221
 
 
222
 
    def __hash__(self):
223
 
        return hash((type(self), self.revision_index, self.inventory_index,
224
 
                     self.text_index, self.signature_index, self.chk_index))
 
232
        setattr(self, index_type + '_index',
 
233
            self.index_class(self.index_transport,
 
234
                self.index_name(index_type, self.name),
 
235
                self.index_sizes[self.index_offset(index_type)],
 
236
                unlimited_cache=unlimited_cache))
225
237
 
226
238
 
227
239
class ExistingPack(Pack):
228
240
    """An in memory proxy for an existing .pack and its disk indices."""
229
241
 
230
242
    def __init__(self, pack_transport, name, revision_index, inventory_index,
231
 
                 text_index, signature_index, chk_index=None):
 
243
        text_index, signature_index, chk_index=None):
232
244
        """Create an ExistingPack object.
233
245
 
234
246
        :param pack_transport: The transport where the pack file resides.
235
247
        :param name: The name of the pack on disk in the pack_transport.
236
248
        """
237
249
        Pack.__init__(self, revision_index, inventory_index, text_index,
238
 
                      signature_index, chk_index)
 
250
            signature_index, chk_index)
239
251
        self.name = name
240
252
        self.pack_transport = pack_transport
241
253
        if None in (revision_index, inventory_index, text_index,
242
 
                    signature_index, name, pack_transport):
 
254
                signature_index, name, pack_transport):
243
255
            raise AssertionError()
244
256
 
245
257
    def __eq__(self, other):
253
265
            self.__class__.__module__, self.__class__.__name__, id(self),
254
266
            self.pack_transport, self.name)
255
267
 
256
 
    def __hash__(self):
257
 
        return hash((type(self), self.name))
258
 
 
259
268
 
260
269
class ResumedPack(ExistingPack):
261
270
 
262
271
    def __init__(self, name, revision_index, inventory_index, text_index,
263
 
                 signature_index, upload_transport, pack_transport, index_transport,
264
 
                 pack_collection, chk_index=None):
 
272
        signature_index, upload_transport, pack_transport, index_transport,
 
273
        pack_collection, chk_index=None):
265
274
        """Create a ResumedPack object."""
266
275
        ExistingPack.__init__(self, pack_transport, name, revision_index,
267
 
                              inventory_index, text_index, signature_index,
268
 
                              chk_index=chk_index)
 
276
            inventory_index, text_index, signature_index,
 
277
            chk_index=chk_index)
269
278
        self.upload_transport = upload_transport
270
279
        self.index_transport = index_transport
271
280
        self.index_sizes = [None, None, None, None]
297
306
    def abort(self):
298
307
        self.upload_transport.delete(self.file_name())
299
308
        indices = [self.revision_index, self.inventory_index, self.text_index,
300
 
                   self.signature_index]
 
309
            self.signature_index]
301
310
        if self.chk_index is not None:
302
311
            indices.append(self.chk_index)
303
312
        for index in indices:
311
320
        for index_type in index_types:
312
321
            old_name = self.index_name(index_type, self.name)
313
322
            new_name = '../indices/' + old_name
314
 
            self.upload_transport.move(old_name, new_name)
 
323
            self.upload_transport.rename(old_name, new_name)
315
324
            self._replace_index_with_readonly(index_type)
316
325
        new_name = '../packs/' + self.file_name()
317
 
        self.upload_transport.move(self.file_name(), new_name)
 
326
        self.upload_transport.rename(self.file_name(), new_name)
318
327
        self._state = 'finished'
319
328
 
320
329
    def _get_external_refs(self, index):
345
354
        else:
346
355
            chk_index = None
347
356
        Pack.__init__(self,
348
 
                      # Revisions: parents list, no text compression.
349
 
                      index_builder_class(reference_lists=1),
350
 
                      # Inventory: We want to map compression only, but currently the
351
 
                      # knit code hasn't been updated enough to understand that, so we
352
 
                      # have a regular 2-list index giving parents and compression
353
 
                      # source.
354
 
                      index_builder_class(reference_lists=2),
355
 
                      # Texts: compression and per file graph, for all fileids - so two
356
 
                      # reference lists and two elements in the key tuple.
357
 
                      index_builder_class(reference_lists=2, key_elements=2),
358
 
                      # Signatures: Just blobs to store, no compression, no parents
359
 
                      # listing.
360
 
                      index_builder_class(reference_lists=0),
361
 
                      # CHK based storage - just blobs, no compression or parents.
362
 
                      chk_index=chk_index
363
 
                      )
 
357
            # Revisions: parents list, no text compression.
 
358
            index_builder_class(reference_lists=1),
 
359
            # Inventory: We want to map compression only, but currently the
 
360
            # knit code hasn't been updated enough to understand that, so we
 
361
            # have a regular 2-list index giving parents and compression
 
362
            # source.
 
363
            index_builder_class(reference_lists=2),
 
364
            # Texts: compression and per file graph, for all fileids - so two
 
365
            # reference lists and two elements in the key tuple.
 
366
            index_builder_class(reference_lists=2, key_elements=2),
 
367
            # Signatures: Just blobs to store, no compression, no parents
 
368
            # listing.
 
369
            index_builder_class(reference_lists=0),
 
370
            # CHK based storage - just blobs, no compression or parents.
 
371
            chk_index=chk_index
 
372
            )
364
373
        self._pack_collection = pack_collection
365
374
        # When we make readonly indices, we need this.
366
375
        self.index_class = pack_collection._index_class
391
400
            self.random_name, mode=self._file_mode)
392
401
        if 'pack' in debug.debug_flags:
393
402
            mutter('%s: create_pack: pack stream open: %s%s t+%6.3fs',
394
 
                   time.ctime(), self.upload_transport.base, self.random_name,
395
 
                   time.time() - self.start_time)
 
403
                time.ctime(), self.upload_transport.base, self.random_name,
 
404
                time.time() - self.start_time)
396
405
        # A list of byte sequences to be written to the new pack, and the
397
406
        # aggregate size of them.  Stored as a list rather than separate
398
407
        # variables so that the _write_data closure below can update them.
402
411
        # robertc says- this is a closure rather than a method on the object
403
412
        # so that the variables are locals, and faster than accessing object
404
413
        # members.
405
 
 
406
414
        def _write_data(bytes, flush=False, _buffer=self._buffer,
407
 
                        _write=self.write_stream.write, _update=self._hash.update):
 
415
            _write=self.write_stream.write, _update=self._hash.update):
408
416
            _buffer[0].append(bytes)
409
417
            _buffer[1] += len(bytes)
410
418
            # buffer cap
411
419
            if _buffer[1] > self._cache_limit or flush:
412
 
                bytes = b''.join(_buffer[0])
 
420
                bytes = ''.join(_buffer[0])
413
421
                _write(bytes)
414
422
                _update(bytes)
415
423
                _buffer[:] = [[], 0]
443
451
    def data_inserted(self):
444
452
        """True if data has been added to this pack."""
445
453
        return bool(self.get_revision_count() or
446
 
                    self.inventory_index.key_count() or
447
 
                    self.text_index.key_count() or
448
 
                    self.signature_index.key_count() or
449
 
                    (self.chk_index is not None and self.chk_index.key_count()))
 
454
            self.inventory_index.key_count() or
 
455
            self.text_index.key_count() or
 
456
            self.signature_index.key_count() or
 
457
            (self.chk_index is not None and self.chk_index.key_count()))
450
458
 
451
459
    def finish_content(self):
452
460
        if self.name is not None:
453
461
            return
454
462
        self._writer.end()
455
463
        if self._buffer[1]:
456
 
            self._write_data(b'', flush=True)
 
464
            self._write_data('', flush=True)
457
465
        self.name = self._hash.hexdigest()
458
466
 
459
467
    def finish(self, suspend=False):
476
484
        # visible is smaller.  On the other hand none will be seen until
477
485
        # they're in the names list.
478
486
        self.index_sizes = [None, None, None, None]
479
 
        self._write_index('revision', self.revision_index, 'revision',
480
 
                          suspend)
 
487
        self._write_index('revision', self.revision_index, 'revision', suspend)
481
488
        self._write_index('inventory', self.inventory_index, 'inventory',
482
 
                          suspend)
 
489
            suspend)
483
490
        self._write_index('text', self.text_index, 'file texts', suspend)
484
491
        self._write_index('signature', self.signature_index,
485
 
                          'revision signatures', suspend)
 
492
            'revision signatures', suspend)
486
493
        if self.chk_index is not None:
487
494
            self.index_sizes.append(None)
488
495
            self._write_index('chk', self.chk_index,
489
 
                              'content hash bytes', suspend)
490
 
        self.write_stream.close(
491
 
            want_fdatasync=self._pack_collection.config_stack.get('repository.fdatasync'))
 
496
                'content hash bytes', suspend)
 
497
        self.write_stream.close()
492
498
        # Note that this will clobber an existing pack with the same name,
493
499
        # without checking for hash collisions. While this is undesirable this
494
500
        # is something that can be rectified in a subsequent release. One way
503
509
        new_name = self.name + '.pack'
504
510
        if not suspend:
505
511
            new_name = '../packs/' + new_name
506
 
        self.upload_transport.move(self.random_name, new_name)
 
512
        self.upload_transport.rename(self.random_name, new_name)
507
513
        self._state = 'finished'
508
514
        if 'pack' in debug.debug_flags:
509
515
            # XXX: size might be interesting?
510
516
            mutter('%s: create_pack: pack finished: %s%s->%s t+%6.3fs',
511
 
                   time.ctime(), self.upload_transport.base, self.random_name,
512
 
                   new_name, time.time() - self.start_time)
 
517
                time.ctime(), self.upload_transport.base, self.random_name,
 
518
                new_name, time.time() - self.start_time)
513
519
 
514
520
    def flush(self):
515
521
        """Flush any current data."""
516
522
        if self._buffer[1]:
517
 
            bytes = b''.join(self._buffer[0])
 
523
            bytes = ''.join(self._buffer[0])
518
524
            self.write_stream.write(bytes)
519
525
            self._hash.update(bytes)
520
526
            self._buffer[:] = [[], 0]
537
543
            transport = self.upload_transport
538
544
        else:
539
545
            transport = self.index_transport
540
 
        index_tempfile = index.finish()
541
 
        index_bytes = index_tempfile.read()
542
 
        write_stream = transport.open_write_stream(index_name,
543
 
                                                   mode=self._file_mode)
544
 
        write_stream.write(index_bytes)
545
 
        write_stream.close(
546
 
            want_fdatasync=self._pack_collection.config_stack.get('repository.fdatasync'))
547
 
        self.index_sizes[self.index_offset(index_type)] = len(index_bytes)
 
546
        self.index_sizes[self.index_offset(index_type)] = transport.put_file(
 
547
            index_name, index.finish(), mode=self._file_mode)
548
548
        if 'pack' in debug.debug_flags:
549
549
            # XXX: size might be interesting?
550
550
            mutter('%s: create_pack: wrote %s index: %s%s t+%6.3fs',
551
 
                   time.ctime(), label, self.upload_transport.base,
552
 
                   self.random_name, time.time() - self.start_time)
 
551
                time.ctime(), label, self.upload_transport.base,
 
552
                self.random_name, time.time() - self.start_time)
553
553
        # Replace the writable index on this object with a readonly,
554
554
        # presently unloaded index. We should alter
555
555
        # the index layer to make its finish() error if add_node is
612
612
        """
613
613
        if self.add_callback is not None:
614
614
            raise AssertionError(
615
 
                "%s already has a writable index through %s" %
 
615
                "%s already has a writable index through %s" % \
616
616
                (self, self.add_callback))
617
617
        # allow writing: queue writes to a new index
618
618
        self.add_index(index, pack)
638
638
        del self.combined_index._indices[pos]
639
639
        del self.combined_index._index_names[pos]
640
640
        if (self.add_callback is not None and
641
 
                getattr(index, 'add_nodes', None) == self.add_callback):
 
641
            getattr(index, 'add_nodes', None) == self.add_callback):
642
642
            self.add_callback = None
643
643
            self.data_access.set_writer(None, None, (None, None))
644
644
 
672
672
        # What text keys to copy. None for 'all texts'. This is set by
673
673
        # _copy_inventory_texts
674
674
        self._text_filter = None
 
675
        self._extra_init()
 
676
 
 
677
    def _extra_init(self):
 
678
        """A template hook to allow extending the constructor trivially."""
 
679
 
 
680
    def _pack_map_and_index_list(self, index_attribute):
 
681
        """Convert a list of packs to an index pack map and index list.
 
682
 
 
683
        :param index_attribute: The attribute that the desired index is found
 
684
            on.
 
685
        :return: A tuple (map, list) where map contains the dict from
 
686
            index:pack_tuple, and list contains the indices in the preferred
 
687
            access order.
 
688
        """
 
689
        indices = []
 
690
        pack_map = {}
 
691
        for pack_obj in self.packs:
 
692
            index = getattr(pack_obj, index_attribute)
 
693
            indices.append(index)
 
694
            pack_map[index] = pack_obj
 
695
        return pack_map, indices
 
696
 
 
697
    def _index_contents(self, indices, key_filter=None):
 
698
        """Get an iterable of the index contents from a pack_map.
 
699
 
 
700
        :param indices: The list of indices to query
 
701
        :param key_filter: An optional filter to limit the keys returned.
 
702
        """
 
703
        all_index = CombinedGraphIndex(indices)
 
704
        if key_filter is None:
 
705
            return all_index.iter_all_entries()
 
706
        else:
 
707
            return all_index.iter_entries(key_filter)
675
708
 
676
709
    def pack(self, pb=None):
677
710
        """Create a new pack by reading data from other packs.
688
721
        :return: A Pack object, or None if nothing was copied.
689
722
        """
690
723
        # open a pack - using the same name as the last temporary file
691
 
        # - which has already been flushed, so it's safe.
 
724
        # - which has already been flushed, so its safe.
692
725
        # XXX: - duplicate code warning with start_write_group; fix before
693
726
        #      considering 'done'.
694
727
        if self._pack_collection._new_pack is not None:
702
735
            else:
703
736
                self.revision_ids = frozenset(self.revision_ids)
704
737
                self.revision_keys = frozenset((revid,) for revid in
705
 
                                               self.revision_ids)
 
738
                    self.revision_ids)
706
739
        if pb is None:
707
740
            self.pb = ui.ui_factory.nested_progress_bar()
708
741
        else:
716
749
    def open_pack(self):
717
750
        """Open a pack for the pack we are creating."""
718
751
        new_pack = self._pack_collection.pack_factory(self._pack_collection,
719
 
                                                      upload_suffix=self.suffix,
720
 
                                                      file_mode=self._pack_collection.repo.controldir._get_file_mode())
 
752
                upload_suffix=self.suffix,
 
753
                file_mode=self._pack_collection.repo.bzrdir._get_file_mode())
721
754
        # We know that we will process all nodes in order, and don't need to
722
755
        # query, so don't combine any indices spilled to disk until we are done
723
756
        new_pack.revision_index.set_optimize(combine_backing_indices=False)
726
759
        new_pack.signature_index.set_optimize(combine_backing_indices=False)
727
760
        return new_pack
728
761
 
 
762
    def _update_pack_order(self, entries, index_to_pack_map):
 
763
        """Determine how we want our packs to be ordered.
 
764
 
 
765
        This changes the sort order of the self.packs list so that packs unused
 
766
        by 'entries' will be at the end of the list, so that future requests
 
767
        can avoid probing them.  Used packs will be at the front of the
 
768
        self.packs list, in the order of their first use in 'entries'.
 
769
 
 
770
        :param entries: A list of (index, ...) tuples
 
771
        :param index_to_pack_map: A mapping from index objects to pack objects.
 
772
        """
 
773
        packs = []
 
774
        seen_indexes = set()
 
775
        for entry in entries:
 
776
            index = entry[0]
 
777
            if index not in seen_indexes:
 
778
                packs.append(index_to_pack_map[index])
 
779
                seen_indexes.add(index)
 
780
        if len(packs) == len(self.packs):
 
781
            if 'pack' in debug.debug_flags:
 
782
                mutter('Not changing pack list, all packs used.')
 
783
            return
 
784
        seen_packs = set(packs)
 
785
        for pack in self.packs:
 
786
            if pack not in seen_packs:
 
787
                packs.append(pack)
 
788
                seen_packs.add(pack)
 
789
        if 'pack' in debug.debug_flags:
 
790
            old_names = [p.access_tuple()[1] for p in self.packs]
 
791
            new_names = [p.access_tuple()[1] for p in packs]
 
792
            mutter('Reordering packs\nfrom: %s\n  to: %s',
 
793
                   old_names, new_names)
 
794
        self.packs = packs
 
795
 
729
796
    def _copy_revision_texts(self):
730
797
        """Copy revision data to the new pack."""
731
 
        raise NotImplementedError(self._copy_revision_texts)
 
798
        # select revisions
 
799
        if self.revision_ids:
 
800
            revision_keys = [(revision_id,) for revision_id in self.revision_ids]
 
801
        else:
 
802
            revision_keys = None
 
803
        # select revision keys
 
804
        revision_index_map, revision_indices = self._pack_map_and_index_list(
 
805
            'revision_index')
 
806
        revision_nodes = self._index_contents(revision_indices, revision_keys)
 
807
        revision_nodes = list(revision_nodes)
 
808
        self._update_pack_order(revision_nodes, revision_index_map)
 
809
        # copy revision keys and adjust values
 
810
        self.pb.update("Copying revision texts", 1)
 
811
        total_items, readv_group_iter = self._revision_node_readv(revision_nodes)
 
812
        list(self._copy_nodes_graph(revision_index_map, self.new_pack._writer,
 
813
            self.new_pack.revision_index, readv_group_iter, total_items))
 
814
        if 'pack' in debug.debug_flags:
 
815
            mutter('%s: create_pack: revisions copied: %s%s %d items t+%6.3fs',
 
816
                time.ctime(), self._pack_collection._upload_transport.base,
 
817
                self.new_pack.random_name,
 
818
                self.new_pack.revision_index.key_count(),
 
819
                time.time() - self.new_pack.start_time)
 
820
        self._revision_keys = revision_keys
732
821
 
733
822
    def _copy_inventory_texts(self):
734
823
        """Copy the inventory texts to the new pack.
737
826
 
738
827
        Sets self._text_filter appropriately.
739
828
        """
740
 
        raise NotImplementedError(self._copy_inventory_texts)
 
829
        # select inventory keys
 
830
        inv_keys = self._revision_keys # currently the same keyspace, and note that
 
831
        # querying for keys here could introduce a bug where an inventory item
 
832
        # is missed, so do not change it to query separately without cross
 
833
        # checking like the text key check below.
 
834
        inventory_index_map, inventory_indices = self._pack_map_and_index_list(
 
835
            'inventory_index')
 
836
        inv_nodes = self._index_contents(inventory_indices, inv_keys)
 
837
        # copy inventory keys and adjust values
 
838
        # XXX: Should be a helper function to allow different inv representation
 
839
        # at this point.
 
840
        self.pb.update("Copying inventory texts", 2)
 
841
        total_items, readv_group_iter = self._least_readv_node_readv(inv_nodes)
 
842
        # Only grab the output lines if we will be processing them
 
843
        output_lines = bool(self.revision_ids)
 
844
        inv_lines = self._copy_nodes_graph(inventory_index_map,
 
845
            self.new_pack._writer, self.new_pack.inventory_index,
 
846
            readv_group_iter, total_items, output_lines=output_lines)
 
847
        if self.revision_ids:
 
848
            self._process_inventory_lines(inv_lines)
 
849
        else:
 
850
            # eat the iterator to cause it to execute.
 
851
            list(inv_lines)
 
852
            self._text_filter = None
 
853
        if 'pack' in debug.debug_flags:
 
854
            mutter('%s: create_pack: inventories copied: %s%s %d items t+%6.3fs',
 
855
                time.ctime(), self._pack_collection._upload_transport.base,
 
856
                self.new_pack.random_name,
 
857
                self.new_pack.inventory_index.key_count(),
 
858
                time.time() - self.new_pack.start_time)
741
859
 
742
860
    def _copy_text_texts(self):
743
 
        raise NotImplementedError(self._copy_text_texts)
 
861
        # select text keys
 
862
        text_index_map, text_nodes = self._get_text_nodes()
 
863
        if self._text_filter is not None:
 
864
            # We could return the keys copied as part of the return value from
 
865
            # _copy_nodes_graph but this doesn't work all that well with the
 
866
            # need to get line output too, so we check separately, and as we're
 
867
            # going to buffer everything anyway, we check beforehand, which
 
868
            # saves reading knit data over the wire when we know there are
 
869
            # mising records.
 
870
            text_nodes = set(text_nodes)
 
871
            present_text_keys = set(_node[1] for _node in text_nodes)
 
872
            missing_text_keys = set(self._text_filter) - present_text_keys
 
873
            if missing_text_keys:
 
874
                # TODO: raise a specific error that can handle many missing
 
875
                # keys.
 
876
                mutter("missing keys during fetch: %r", missing_text_keys)
 
877
                a_missing_key = missing_text_keys.pop()
 
878
                raise errors.RevisionNotPresent(a_missing_key[1],
 
879
                    a_missing_key[0])
 
880
        # copy text keys and adjust values
 
881
        self.pb.update("Copying content texts", 3)
 
882
        total_items, readv_group_iter = self._least_readv_node_readv(text_nodes)
 
883
        list(self._copy_nodes_graph(text_index_map, self.new_pack._writer,
 
884
            self.new_pack.text_index, readv_group_iter, total_items))
 
885
        self._log_copied_texts()
744
886
 
745
887
    def _create_pack_from_packs(self):
746
 
        raise NotImplementedError(self._create_pack_from_packs)
 
888
        self.pb.update("Opening pack", 0, 5)
 
889
        self.new_pack = self.open_pack()
 
890
        new_pack = self.new_pack
 
891
        # buffer data - we won't be reading-back during the pack creation and
 
892
        # this makes a significant difference on sftp pushes.
 
893
        new_pack.set_write_cache_size(1024*1024)
 
894
        if 'pack' in debug.debug_flags:
 
895
            plain_pack_list = ['%s%s' % (a_pack.pack_transport.base, a_pack.name)
 
896
                for a_pack in self.packs]
 
897
            if self.revision_ids is not None:
 
898
                rev_count = len(self.revision_ids)
 
899
            else:
 
900
                rev_count = 'all'
 
901
            mutter('%s: create_pack: creating pack from source packs: '
 
902
                '%s%s %s revisions wanted %s t=0',
 
903
                time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
 
904
                plain_pack_list, rev_count)
 
905
        self._copy_revision_texts()
 
906
        self._copy_inventory_texts()
 
907
        self._copy_text_texts()
 
908
        # select signature keys
 
909
        signature_filter = self._revision_keys # same keyspace
 
910
        signature_index_map, signature_indices = self._pack_map_and_index_list(
 
911
            'signature_index')
 
912
        signature_nodes = self._index_contents(signature_indices,
 
913
            signature_filter)
 
914
        # copy signature keys and adjust values
 
915
        self.pb.update("Copying signature texts", 4)
 
916
        self._copy_nodes(signature_nodes, signature_index_map, new_pack._writer,
 
917
            new_pack.signature_index)
 
918
        if 'pack' in debug.debug_flags:
 
919
            mutter('%s: create_pack: revision signatures copied: %s%s %d items t+%6.3fs',
 
920
                time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
 
921
                new_pack.signature_index.key_count(),
 
922
                time.time() - new_pack.start_time)
 
923
        # copy chk contents
 
924
        # NB XXX: how to check CHK references are present? perhaps by yielding
 
925
        # the items? How should that interact with stacked repos?
 
926
        if new_pack.chk_index is not None:
 
927
            self._copy_chks()
 
928
            if 'pack' in debug.debug_flags:
 
929
                mutter('%s: create_pack: chk content copied: %s%s %d items t+%6.3fs',
 
930
                    time.ctime(), self._pack_collection._upload_transport.base,
 
931
                    new_pack.random_name,
 
932
                    new_pack.chk_index.key_count(),
 
933
                    time.time() - new_pack.start_time)
 
934
        new_pack._check_references()
 
935
        if not self._use_pack(new_pack):
 
936
            new_pack.abort()
 
937
            return None
 
938
        self.pb.update("Finishing pack", 5)
 
939
        new_pack.finish()
 
940
        self._pack_collection.allocate(new_pack)
 
941
        return new_pack
 
942
 
 
943
    def _copy_chks(self, refs=None):
 
944
        # XXX: Todo, recursive follow-pointers facility when fetching some
 
945
        # revisions only.
 
946
        chk_index_map, chk_indices = self._pack_map_and_index_list(
 
947
            'chk_index')
 
948
        chk_nodes = self._index_contents(chk_indices, refs)
 
949
        new_refs = set()
 
950
        # TODO: This isn't strictly tasteful as we are accessing some private
 
951
        #       variables (_serializer). Perhaps a better way would be to have
 
952
        #       Repository._deserialise_chk_node()
 
953
        search_key_func = chk_map.search_key_registry.get(
 
954
            self._pack_collection.repo._serializer.search_key_name)
 
955
        def accumlate_refs(lines):
 
956
            # XXX: move to a generic location
 
957
            # Yay mismatch:
 
958
            bytes = ''.join(lines)
 
959
            node = chk_map._deserialise(bytes, ("unknown",), search_key_func)
 
960
            new_refs.update(node.refs())
 
961
        self._copy_nodes(chk_nodes, chk_index_map, self.new_pack._writer,
 
962
            self.new_pack.chk_index, output_lines=accumlate_refs)
 
963
        return new_refs
 
964
 
 
965
    def _copy_nodes(self, nodes, index_map, writer, write_index,
 
966
        output_lines=None):
 
967
        """Copy knit nodes between packs with no graph references.
 
968
 
 
969
        :param output_lines: Output full texts of copied items.
 
970
        """
 
971
        pb = ui.ui_factory.nested_progress_bar()
 
972
        try:
 
973
            return self._do_copy_nodes(nodes, index_map, writer,
 
974
                write_index, pb, output_lines=output_lines)
 
975
        finally:
 
976
            pb.finished()
 
977
 
 
978
    def _do_copy_nodes(self, nodes, index_map, writer, write_index, pb,
 
979
        output_lines=None):
 
980
        # for record verification
 
981
        knit = KnitVersionedFiles(None, None)
 
982
        # plan a readv on each source pack:
 
983
        # group by pack
 
984
        nodes = sorted(nodes)
 
985
        # how to map this into knit.py - or knit.py into this?
 
986
        # we don't want the typical knit logic, we want grouping by pack
 
987
        # at this point - perhaps a helper library for the following code
 
988
        # duplication points?
 
989
        request_groups = {}
 
990
        for index, key, value in nodes:
 
991
            if index not in request_groups:
 
992
                request_groups[index] = []
 
993
            request_groups[index].append((key, value))
 
994
        record_index = 0
 
995
        pb.update("Copied record", record_index, len(nodes))
 
996
        for index, items in request_groups.iteritems():
 
997
            pack_readv_requests = []
 
998
            for key, value in items:
 
999
                # ---- KnitGraphIndex.get_position
 
1000
                bits = value[1:].split(' ')
 
1001
                offset, length = int(bits[0]), int(bits[1])
 
1002
                pack_readv_requests.append((offset, length, (key, value[0])))
 
1003
            # linear scan up the pack
 
1004
            pack_readv_requests.sort()
 
1005
            # copy the data
 
1006
            pack_obj = index_map[index]
 
1007
            transport, path = pack_obj.access_tuple()
 
1008
            try:
 
1009
                reader = pack.make_readv_reader(transport, path,
 
1010
                    [offset[0:2] for offset in pack_readv_requests])
 
1011
            except errors.NoSuchFile:
 
1012
                if self._reload_func is not None:
 
1013
                    self._reload_func()
 
1014
                raise
 
1015
            for (names, read_func), (_1, _2, (key, eol_flag)) in \
 
1016
                izip(reader.iter_records(), pack_readv_requests):
 
1017
                raw_data = read_func(None)
 
1018
                # check the header only
 
1019
                if output_lines is not None:
 
1020
                    output_lines(knit._parse_record(key[-1], raw_data)[0])
 
1021
                else:
 
1022
                    df, _ = knit._parse_record_header(key, raw_data)
 
1023
                    df.close()
 
1024
                pos, size = writer.add_bytes_record(raw_data, names)
 
1025
                write_index.add_node(key, eol_flag + "%d %d" % (pos, size))
 
1026
                pb.update("Copied record", record_index)
 
1027
                record_index += 1
 
1028
 
 
1029
    def _copy_nodes_graph(self, index_map, writer, write_index,
 
1030
        readv_group_iter, total_items, output_lines=False):
 
1031
        """Copy knit nodes between packs.
 
1032
 
 
1033
        :param output_lines: Return lines present in the copied data as
 
1034
            an iterator of line,version_id.
 
1035
        """
 
1036
        pb = ui.ui_factory.nested_progress_bar()
 
1037
        try:
 
1038
            for result in self._do_copy_nodes_graph(index_map, writer,
 
1039
                write_index, output_lines, pb, readv_group_iter, total_items):
 
1040
                yield result
 
1041
        except Exception:
 
1042
            # Python 2.4 does not permit try:finally: in a generator.
 
1043
            pb.finished()
 
1044
            raise
 
1045
        else:
 
1046
            pb.finished()
 
1047
 
 
1048
    def _do_copy_nodes_graph(self, index_map, writer, write_index,
 
1049
        output_lines, pb, readv_group_iter, total_items):
 
1050
        # for record verification
 
1051
        knit = KnitVersionedFiles(None, None)
 
1052
        # for line extraction when requested (inventories only)
 
1053
        if output_lines:
 
1054
            factory = KnitPlainFactory()
 
1055
        record_index = 0
 
1056
        pb.update("Copied record", record_index, total_items)
 
1057
        for index, readv_vector, node_vector in readv_group_iter:
 
1058
            # copy the data
 
1059
            pack_obj = index_map[index]
 
1060
            transport, path = pack_obj.access_tuple()
 
1061
            try:
 
1062
                reader = pack.make_readv_reader(transport, path, readv_vector)
 
1063
            except errors.NoSuchFile:
 
1064
                if self._reload_func is not None:
 
1065
                    self._reload_func()
 
1066
                raise
 
1067
            for (names, read_func), (key, eol_flag, references) in \
 
1068
                izip(reader.iter_records(), node_vector):
 
1069
                raw_data = read_func(None)
 
1070
                if output_lines:
 
1071
                    # read the entire thing
 
1072
                    content, _ = knit._parse_record(key[-1], raw_data)
 
1073
                    if len(references[-1]) == 0:
 
1074
                        line_iterator = factory.get_fulltext_content(content)
 
1075
                    else:
 
1076
                        line_iterator = factory.get_linedelta_content(content)
 
1077
                    for line in line_iterator:
 
1078
                        yield line, key
 
1079
                else:
 
1080
                    # check the header only
 
1081
                    df, _ = knit._parse_record_header(key, raw_data)
 
1082
                    df.close()
 
1083
                pos, size = writer.add_bytes_record(raw_data, names)
 
1084
                write_index.add_node(key, eol_flag + "%d %d" % (pos, size), references)
 
1085
                pb.update("Copied record", record_index)
 
1086
                record_index += 1
 
1087
 
 
1088
    def _get_text_nodes(self):
 
1089
        text_index_map, text_indices = self._pack_map_and_index_list(
 
1090
            'text_index')
 
1091
        return text_index_map, self._index_contents(text_indices,
 
1092
            self._text_filter)
 
1093
 
 
1094
    def _least_readv_node_readv(self, nodes):
 
1095
        """Generate request groups for nodes using the least readv's.
 
1096
 
 
1097
        :param nodes: An iterable of graph index nodes.
 
1098
        :return: Total node count and an iterator of the data needed to perform
 
1099
            readvs to obtain the data for nodes. Each item yielded by the
 
1100
            iterator is a tuple with:
 
1101
            index, readv_vector, node_vector. readv_vector is a list ready to
 
1102
            hand to the transport readv method, and node_vector is a list of
 
1103
            (key, eol_flag, references) for the node retrieved by the
 
1104
            matching readv_vector.
 
1105
        """
 
1106
        # group by pack so we do one readv per pack
 
1107
        nodes = sorted(nodes)
 
1108
        total = len(nodes)
 
1109
        request_groups = {}
 
1110
        for index, key, value, references in nodes:
 
1111
            if index not in request_groups:
 
1112
                request_groups[index] = []
 
1113
            request_groups[index].append((key, value, references))
 
1114
        result = []
 
1115
        for index, items in request_groups.iteritems():
 
1116
            pack_readv_requests = []
 
1117
            for key, value, references in items:
 
1118
                # ---- KnitGraphIndex.get_position
 
1119
                bits = value[1:].split(' ')
 
1120
                offset, length = int(bits[0]), int(bits[1])
 
1121
                pack_readv_requests.append(
 
1122
                    ((offset, length), (key, value[0], references)))
 
1123
            # linear scan up the pack to maximum range combining.
 
1124
            pack_readv_requests.sort()
 
1125
            # split out the readv and the node data.
 
1126
            pack_readv = [readv for readv, node in pack_readv_requests]
 
1127
            node_vector = [node for readv, node in pack_readv_requests]
 
1128
            result.append((index, pack_readv, node_vector))
 
1129
        return total, result
747
1130
 
748
1131
    def _log_copied_texts(self):
749
1132
        if 'pack' in debug.debug_flags:
750
1133
            mutter('%s: create_pack: file texts copied: %s%s %d items t+%6.3fs',
751
 
                   time.ctime(), self._pack_collection._upload_transport.base,
752
 
                   self.new_pack.random_name,
753
 
                   self.new_pack.text_index.key_count(),
754
 
                   time.time() - self.new_pack.start_time)
 
1134
                time.ctime(), self._pack_collection._upload_transport.base,
 
1135
                self.new_pack.random_name,
 
1136
                self.new_pack.text_index.key_count(),
 
1137
                time.time() - self.new_pack.start_time)
 
1138
 
 
1139
    def _process_inventory_lines(self, inv_lines):
 
1140
        """Use up the inv_lines generator and setup a text key filter."""
 
1141
        repo = self._pack_collection.repo
 
1142
        fileid_revisions = repo._find_file_ids_from_xml_inventory_lines(
 
1143
            inv_lines, self.revision_keys)
 
1144
        text_filter = []
 
1145
        for fileid, file_revids in fileid_revisions.iteritems():
 
1146
            text_filter.extend([(fileid, file_revid) for file_revid in file_revids])
 
1147
        self._text_filter = text_filter
 
1148
 
 
1149
    def _revision_node_readv(self, revision_nodes):
 
1150
        """Return the total revisions and the readv's to issue.
 
1151
 
 
1152
        :param revision_nodes: The revision index contents for the packs being
 
1153
            incorporated into the new pack.
 
1154
        :return: As per _least_readv_node_readv.
 
1155
        """
 
1156
        return self._least_readv_node_readv(revision_nodes)
755
1157
 
756
1158
    def _use_pack(self, new_pack):
757
1159
        """Return True if new_pack should be used.
762
1164
        return new_pack.data_inserted()
763
1165
 
764
1166
 
 
1167
class OptimisingPacker(Packer):
 
1168
    """A packer which spends more time to create better disk layouts."""
 
1169
 
 
1170
    def _revision_node_readv(self, revision_nodes):
 
1171
        """Return the total revisions and the readv's to issue.
 
1172
 
 
1173
        This sort places revisions in topological order with the ancestors
 
1174
        after the children.
 
1175
 
 
1176
        :param revision_nodes: The revision index contents for the packs being
 
1177
            incorporated into the new pack.
 
1178
        :return: As per _least_readv_node_readv.
 
1179
        """
 
1180
        # build an ancestors dict
 
1181
        ancestors = {}
 
1182
        by_key = {}
 
1183
        for index, key, value, references in revision_nodes:
 
1184
            ancestors[key] = references[0]
 
1185
            by_key[key] = (index, value, references)
 
1186
        order = tsort.topo_sort(ancestors)
 
1187
        total = len(order)
 
1188
        # Single IO is pathological, but it will work as a starting point.
 
1189
        requests = []
 
1190
        for key in reversed(order):
 
1191
            index, value, references = by_key[key]
 
1192
            # ---- KnitGraphIndex.get_position
 
1193
            bits = value[1:].split(' ')
 
1194
            offset, length = int(bits[0]), int(bits[1])
 
1195
            requests.append(
 
1196
                (index, [(offset, length)], [(key, value[0], references)]))
 
1197
        # TODO: combine requests in the same index that are in ascending order.
 
1198
        return total, requests
 
1199
 
 
1200
    def open_pack(self):
 
1201
        """Open a pack for the pack we are creating."""
 
1202
        new_pack = super(OptimisingPacker, self).open_pack()
 
1203
        # Turn on the optimization flags for all the index builders.
 
1204
        new_pack.revision_index.set_optimize(for_size=True)
 
1205
        new_pack.inventory_index.set_optimize(for_size=True)
 
1206
        new_pack.text_index.set_optimize(for_size=True)
 
1207
        new_pack.signature_index.set_optimize(for_size=True)
 
1208
        return new_pack
 
1209
 
 
1210
 
 
1211
class ReconcilePacker(Packer):
 
1212
    """A packer which regenerates indices etc as it copies.
 
1213
 
 
1214
    This is used by ``bzr reconcile`` to cause parent text pointers to be
 
1215
    regenerated.
 
1216
    """
 
1217
 
 
1218
    def _extra_init(self):
 
1219
        self._data_changed = False
 
1220
 
 
1221
    def _process_inventory_lines(self, inv_lines):
 
1222
        """Generate a text key reference map rather for reconciling with."""
 
1223
        repo = self._pack_collection.repo
 
1224
        refs = repo._find_text_key_references_from_xml_inventory_lines(
 
1225
            inv_lines)
 
1226
        self._text_refs = refs
 
1227
        # during reconcile we:
 
1228
        #  - convert unreferenced texts to full texts
 
1229
        #  - correct texts which reference a text not copied to be full texts
 
1230
        #  - copy all others as-is but with corrected parents.
 
1231
        #  - so at this point we don't know enough to decide what becomes a full
 
1232
        #    text.
 
1233
        self._text_filter = None
 
1234
 
 
1235
    def _copy_text_texts(self):
 
1236
        """generate what texts we should have and then copy."""
 
1237
        self.pb.update("Copying content texts", 3)
 
1238
        # we have three major tasks here:
 
1239
        # 1) generate the ideal index
 
1240
        repo = self._pack_collection.repo
 
1241
        ancestors = dict([(key[0], tuple(ref[0] for ref in refs[0])) for
 
1242
            _1, key, _2, refs in
 
1243
            self.new_pack.revision_index.iter_all_entries()])
 
1244
        ideal_index = repo._generate_text_key_index(self._text_refs, ancestors)
 
1245
        # 2) generate a text_nodes list that contains all the deltas that can
 
1246
        #    be used as-is, with corrected parents.
 
1247
        ok_nodes = []
 
1248
        bad_texts = []
 
1249
        discarded_nodes = []
 
1250
        NULL_REVISION = _mod_revision.NULL_REVISION
 
1251
        text_index_map, text_nodes = self._get_text_nodes()
 
1252
        for node in text_nodes:
 
1253
            # 0 - index
 
1254
            # 1 - key
 
1255
            # 2 - value
 
1256
            # 3 - refs
 
1257
            try:
 
1258
                ideal_parents = tuple(ideal_index[node[1]])
 
1259
            except KeyError:
 
1260
                discarded_nodes.append(node)
 
1261
                self._data_changed = True
 
1262
            else:
 
1263
                if ideal_parents == (NULL_REVISION,):
 
1264
                    ideal_parents = ()
 
1265
                if ideal_parents == node[3][0]:
 
1266
                    # no change needed.
 
1267
                    ok_nodes.append(node)
 
1268
                elif ideal_parents[0:1] == node[3][0][0:1]:
 
1269
                    # the left most parent is the same, or there are no parents
 
1270
                    # today. Either way, we can preserve the representation as
 
1271
                    # long as we change the refs to be inserted.
 
1272
                    self._data_changed = True
 
1273
                    ok_nodes.append((node[0], node[1], node[2],
 
1274
                        (ideal_parents, node[3][1])))
 
1275
                    self._data_changed = True
 
1276
                else:
 
1277
                    # Reinsert this text completely
 
1278
                    bad_texts.append((node[1], ideal_parents))
 
1279
                    self._data_changed = True
 
1280
        # we're finished with some data.
 
1281
        del ideal_index
 
1282
        del text_nodes
 
1283
        # 3) bulk copy the ok data
 
1284
        total_items, readv_group_iter = self._least_readv_node_readv(ok_nodes)
 
1285
        list(self._copy_nodes_graph(text_index_map, self.new_pack._writer,
 
1286
            self.new_pack.text_index, readv_group_iter, total_items))
 
1287
        # 4) adhoc copy all the other texts.
 
1288
        # We have to topologically insert all texts otherwise we can fail to
 
1289
        # reconcile when parts of a single delta chain are preserved intact,
 
1290
        # and other parts are not. E.g. Discarded->d1->d2->d3. d1 will be
 
1291
        # reinserted, and if d3 has incorrect parents it will also be
 
1292
        # reinserted. If we insert d3 first, d2 is present (as it was bulk
 
1293
        # copied), so we will try to delta, but d2 is not currently able to be
 
1294
        # extracted because it's basis d1 is not present. Topologically sorting
 
1295
        # addresses this. The following generates a sort for all the texts that
 
1296
        # are being inserted without having to reference the entire text key
 
1297
        # space (we only topo sort the revisions, which is smaller).
 
1298
        topo_order = tsort.topo_sort(ancestors)
 
1299
        rev_order = dict(zip(topo_order, range(len(topo_order))))
 
1300
        bad_texts.sort(key=lambda key:rev_order.get(key[0][1], 0))
 
1301
        transaction = repo.get_transaction()
 
1302
        file_id_index = GraphIndexPrefixAdapter(
 
1303
            self.new_pack.text_index,
 
1304
            ('blank', ), 1,
 
1305
            add_nodes_callback=self.new_pack.text_index.add_nodes)
 
1306
        data_access = _DirectPackAccess(
 
1307
                {self.new_pack.text_index:self.new_pack.access_tuple()})
 
1308
        data_access.set_writer(self.new_pack._writer, self.new_pack.text_index,
 
1309
            self.new_pack.access_tuple())
 
1310
        output_texts = KnitVersionedFiles(
 
1311
            _KnitGraphIndex(self.new_pack.text_index,
 
1312
                add_callback=self.new_pack.text_index.add_nodes,
 
1313
                deltas=True, parents=True, is_locked=repo.is_locked),
 
1314
            data_access=data_access, max_delta_chain=200)
 
1315
        for key, parent_keys in bad_texts:
 
1316
            # We refer to the new pack to delta data being output.
 
1317
            # A possible improvement would be to catch errors on short reads
 
1318
            # and only flush then.
 
1319
            self.new_pack.flush()
 
1320
            parents = []
 
1321
            for parent_key in parent_keys:
 
1322
                if parent_key[0] != key[0]:
 
1323
                    # Graph parents must match the fileid
 
1324
                    raise errors.BzrError('Mismatched key parent %r:%r' %
 
1325
                        (key, parent_keys))
 
1326
                parents.append(parent_key[1])
 
1327
            text_lines = osutils.split_lines(repo.texts.get_record_stream(
 
1328
                [key], 'unordered', True).next().get_bytes_as('fulltext'))
 
1329
            output_texts.add_lines(key, parent_keys, text_lines,
 
1330
                random_id=True, check_content=False)
 
1331
        # 5) check that nothing inserted has a reference outside the keyspace.
 
1332
        missing_text_keys = self.new_pack.text_index._external_references()
 
1333
        if missing_text_keys:
 
1334
            raise errors.BzrCheckError('Reference to missing compression parents %r'
 
1335
                % (missing_text_keys,))
 
1336
        self._log_copied_texts()
 
1337
 
 
1338
    def _use_pack(self, new_pack):
 
1339
        """Override _use_pack to check for reconcile having changed content."""
 
1340
        # XXX: we might be better checking this at the copy time.
 
1341
        original_inventory_keys = set()
 
1342
        inv_index = self._pack_collection.inventory_index.combined_index
 
1343
        for entry in inv_index.iter_all_entries():
 
1344
            original_inventory_keys.add(entry[1])
 
1345
        new_inventory_keys = set()
 
1346
        for entry in new_pack.inventory_index.iter_all_entries():
 
1347
            new_inventory_keys.add(entry[1])
 
1348
        if new_inventory_keys != original_inventory_keys:
 
1349
            self._data_changed = True
 
1350
        return new_pack.data_inserted() and self._data_changed
 
1351
 
 
1352
 
765
1353
class RepositoryPackCollection(object):
766
1354
    """Management of packs within a repository.
767
1355
 
768
1356
    :ivar _names: map of {pack_name: (index_size,)}
769
1357
    """
770
1358
 
771
 
    pack_factory = None
772
 
    resumed_pack_factory = None
773
 
    normal_packer_class = None
774
 
    optimising_packer_class = None
 
1359
    pack_factory = NewPack
 
1360
    resumed_pack_factory = ResumedPack
775
1361
 
776
1362
    def __init__(self, repo, transport, index_transport, upload_transport,
777
1363
                 pack_transport, index_builder_class, index_class,
797
1383
        self._index_builder_class = index_builder_class
798
1384
        self._index_class = index_class
799
1385
        self._suffix_offsets = {'.rix': 0, '.iix': 1, '.tix': 2, '.six': 3,
800
 
                                '.cix': 4}
 
1386
            '.cix': 4}
801
1387
        self.packs = []
802
1388
        # name:Pack mapping
803
1389
        self._names = None
813
1399
        self.text_index = AggregateIndex(self.reload_pack_names, flush)
814
1400
        self.signature_index = AggregateIndex(self.reload_pack_names, flush)
815
1401
        all_indices = [self.revision_index, self.inventory_index,
816
 
                       self.text_index, self.signature_index]
 
1402
                self.text_index, self.signature_index]
817
1403
        if use_chk_index:
818
1404
            self.chk_index = AggregateIndex(self.reload_pack_names, flush)
819
1405
            all_indices.append(self.chk_index)
828
1414
                set(all_combined).difference([combined_idx]))
829
1415
        # resumed packs
830
1416
        self._resumed_packs = []
831
 
        self.config_stack = config.LocationStack(self.transport.base)
832
1417
 
833
1418
    def __repr__(self):
834
1419
        return '%s(%r)' % (self.__class__.__name__, self.repo)
916
1501
        num_old_packs = sum([len(po[1]) for po in pack_operations])
917
1502
        num_revs_affected = sum([po[0] for po in pack_operations])
918
1503
        mutter('Auto-packing repository %s, which has %d pack files, '
919
 
               'containing %d revisions. Packing %d files into %d affecting %d'
920
 
               ' revisions', str(
921
 
                   self), total_packs, total_revisions, num_old_packs,
922
 
               num_new_packs, num_revs_affected)
923
 
        result = self._execute_pack_operations(pack_operations, packer_class=self.normal_packer_class,
924
 
                                               reload_func=self._restart_autopack)
925
 
        mutter('Auto-packing repository %s completed', str(self))
 
1504
            'containing %d revisions. Packing %d files into %d affecting %d'
 
1505
            ' revisions', self, total_packs, total_revisions, num_old_packs,
 
1506
            num_new_packs, num_revs_affected)
 
1507
        result = self._execute_pack_operations(pack_operations,
 
1508
                                      reload_func=self._restart_autopack)
 
1509
        mutter('Auto-packing repository %s completed', self)
926
1510
        return result
927
1511
 
928
 
    def _execute_pack_operations(self, pack_operations, packer_class,
 
1512
    def _execute_pack_operations(self, pack_operations, _packer_class=Packer,
929
1513
                                 reload_func=None):
930
1514
        """Execute a series of pack operations.
931
1515
 
932
1516
        :param pack_operations: A list of [revision_count, packs_to_combine].
933
 
        :param packer_class: The class of packer to use
 
1517
        :param _packer_class: The class of packer to use (default: Packer).
934
1518
        :return: The new pack names.
935
1519
        """
936
1520
        for revision_count, packs in pack_operations:
937
1521
            # we may have no-ops from the setup logic
938
1522
            if len(packs) == 0:
939
1523
                continue
940
 
            packer = packer_class(self, packs, '.autopack',
941
 
                                  reload_func=reload_func)
 
1524
            packer = _packer_class(self, packs, '.autopack',
 
1525
                                   reload_func=reload_func)
942
1526
            try:
943
 
                result = packer.pack()
 
1527
                packer.pack()
944
1528
            except errors.RetryWithNewPacks:
945
1529
                # An exception is propagating out of this context, make sure
946
1530
                # this packer has cleaned up. Packer() doesn't set its new_pack
949
1533
                if packer.new_pack is not None:
950
1534
                    packer.new_pack.abort()
951
1535
                raise
952
 
            if result is None:
953
 
                return
954
1536
            for pack in packs:
955
1537
                self._remove_pack_from_memory(pack)
956
1538
        # record the newly available packs and stop advertising the old
988
1570
        # XXX: the following may want to be a class, to pack with a given
989
1571
        # policy.
990
1572
        mutter('Packing repository %s, which has %d pack files, '
991
 
               'containing %d revisions with hint %r.', str(self), total_packs,
992
 
               total_revisions, hint)
993
 
        while True:
994
 
            try:
995
 
                self._try_pack_operations(hint)
996
 
            except RetryPackOperations:
997
 
                continue
998
 
            break
999
 
 
1000
 
        if clean_obsolete_packs:
1001
 
            self._clear_obsolete_packs()
1002
 
 
1003
 
    def _try_pack_operations(self, hint):
1004
 
        """Calculate the pack operations based on the hint (if any), and
1005
 
        execute them.
1006
 
        """
 
1573
            'containing %d revisions with hint %r.', self, total_packs,
 
1574
            total_revisions, hint)
1007
1575
        # determine which packs need changing
1008
1576
        pack_operations = [[0, []]]
1009
1577
        for pack in self.all_packs():
1012
1580
                # or this pack was included in the hint.
1013
1581
                pack_operations[-1][0] += pack.get_revision_count()
1014
1582
                pack_operations[-1][1].append(pack)
1015
 
        self._execute_pack_operations(pack_operations,
1016
 
                                      packer_class=self.optimising_packer_class,
1017
 
                                      reload_func=self._restart_pack_operations)
 
1583
        self._execute_pack_operations(pack_operations, OptimisingPacker)
 
1584
 
 
1585
        if clean_obsolete_packs:
 
1586
            self._clear_obsolete_packs()
1018
1587
 
1019
1588
    def plan_autopack_combinations(self, existing_packs, pack_distribution):
1020
1589
        """Plan a pack operation.
1030
1599
        pack_operations = [[0, []]]
1031
1600
        # plan out what packs to keep, and what to reorganise
1032
1601
        while len(existing_packs):
1033
 
            # take the largest pack, and if it's less than the head of the
 
1602
            # take the largest pack, and if its less than the head of the
1034
1603
            # distribution chart we will include its contents in the new pack
1035
 
            # for that position. If it's larger, we remove its size from the
 
1604
            # for that position. If its larger, we remove its size from the
1036
1605
            # distribution chart
1037
1606
            next_pack_rev_count, next_pack = existing_packs.pop(0)
1038
1607
            if next_pack_rev_count >= pack_distribution[0]:
1064
1633
            final_pack_list.extend(pack_files)
1065
1634
        if len(final_pack_list) == 1:
1066
1635
            raise AssertionError('We somehow generated an autopack with a'
1067
 
                                 ' single pack file being moved.')
 
1636
                ' single pack file being moved.')
1068
1637
            return []
1069
1638
        return [[final_rev_count, final_pack_list]]
1070
1639
 
1073
1642
 
1074
1643
        :return: True if the disk names had not been previously read.
1075
1644
        """
1076
 
        # NB: if you see an assertion error here, it's probably access against
 
1645
        # NB: if you see an assertion error here, its probably access against
1077
1646
        # an unlocked repo. Naughty.
1078
1647
        if not self.repo.is_locked():
1079
1648
            raise errors.ObjectNotLocked(self.repo)
1081
1650
            self._names = {}
1082
1651
            self._packs_at_load = set()
1083
1652
            for index, key, value in self._iter_disk_pack_index():
1084
 
                name = key[0].decode('ascii')
 
1653
                name = key[0]
1085
1654
                self._names[name] = self._parse_index_sizes(value)
1086
 
                self._packs_at_load.add((name, value))
 
1655
                self._packs_at_load.add((key, value))
1087
1656
            result = True
1088
1657
        else:
1089
1658
            result = False
1093
1662
 
1094
1663
    def _parse_index_sizes(self, value):
1095
1664
        """Parse a string of index sizes."""
1096
 
        return tuple(int(digits) for digits in value.split(b' '))
 
1665
        return tuple([int(digits) for digits in value.split(' ')])
1097
1666
 
1098
1667
    def get_pack_by_name(self, name):
1099
1668
        """Get a Pack object by name.
1109
1678
            txt_index = self._make_index(name, '.tix')
1110
1679
            sig_index = self._make_index(name, '.six')
1111
1680
            if self.chk_index is not None:
1112
 
                chk_index = self._make_index(name, '.cix', is_chk=True)
 
1681
                chk_index = self._make_index(name, '.cix', unlimited_cache=True)
1113
1682
            else:
1114
1683
                chk_index = None
1115
1684
            result = ExistingPack(self._pack_transport, name, rev_index,
1116
 
                                  inv_index, txt_index, sig_index, chk_index)
 
1685
                inv_index, txt_index, sig_index, chk_index)
1117
1686
            self.add_pack_to_memory(result)
1118
1687
            return result
1119
1688
 
1135
1704
            sig_index = self._make_index(name, '.six', resume=True)
1136
1705
            if self.chk_index is not None:
1137
1706
                chk_index = self._make_index(name, '.cix', resume=True,
1138
 
                                             is_chk=True)
 
1707
                                             unlimited_cache=True)
1139
1708
            else:
1140
1709
                chk_index = None
1141
1710
            result = self.resumed_pack_factory(name, rev_index, inv_index,
1142
 
                                               txt_index, sig_index, self._upload_transport,
1143
 
                                               self._pack_transport, self._index_transport, self,
1144
 
                                               chk_index=chk_index)
1145
 
        except errors.NoSuchFile as e:
 
1711
                txt_index, sig_index, self._upload_transport,
 
1712
                self._pack_transport, self._index_transport, self,
 
1713
                chk_index=chk_index)
 
1714
        except errors.NoSuchFile, e:
1146
1715
            raise errors.UnresumableWriteGroup(self.repo, [name], str(e))
1147
1716
        self.add_pack_to_memory(result)
1148
1717
        self._resumed_packs.append(result)
1169
1738
        :return: An iterator of the index contents.
1170
1739
        """
1171
1740
        return self._index_class(self.transport, 'pack-names', None
1172
 
                                 ).iter_all_entries()
 
1741
                ).iter_all_entries()
1173
1742
 
1174
 
    def _make_index(self, name, suffix, resume=False, is_chk=False):
 
1743
    def _make_index(self, name, suffix, resume=False, unlimited_cache=False):
1175
1744
        size_offset = self._suffix_offsets[suffix]
1176
1745
        index_name = name + suffix
1177
1746
        if resume:
1180
1749
        else:
1181
1750
            transport = self._index_transport
1182
1751
            index_size = self._names[name][size_offset]
1183
 
        index = self._index_class(transport, index_name, index_size,
1184
 
                                  unlimited_cache=is_chk)
1185
 
        if is_chk and self._index_class is btree_index.BTreeGraphIndex:
1186
 
            index._leaf_factory = btree_index._gcchk_factory
1187
 
        return index
 
1752
        return self._index_class(transport, index_name, index_size,
 
1753
                                 unlimited_cache=unlimited_cache)
1188
1754
 
1189
1755
    def _max_pack_count(self, total_revisions):
1190
1756
        """Return the maximum number of packs to use for total revisions.
1219
1785
        """
1220
1786
        for pack in packs:
1221
1787
            try:
1222
 
                try:
1223
 
                    pack.pack_transport.move(pack.file_name(),
1224
 
                                             '../obsolete_packs/' + pack.file_name())
1225
 
                except errors.NoSuchFile:
1226
 
                    # perhaps obsolete_packs was removed? Let's create it and
1227
 
                    # try again
1228
 
                    try:
1229
 
                        pack.pack_transport.mkdir('../obsolete_packs/')
1230
 
                    except errors.FileExists:
1231
 
                        pass
1232
 
                    pack.pack_transport.move(pack.file_name(),
1233
 
                                             '../obsolete_packs/' + pack.file_name())
1234
 
            except (errors.PathError, errors.TransportError) as e:
 
1788
                pack.pack_transport.rename(pack.file_name(),
 
1789
                    '../obsolete_packs/' + pack.file_name())
 
1790
            except (errors.PathError, errors.TransportError), e:
1235
1791
                # TODO: Should these be warnings or mutters?
1236
1792
                mutter("couldn't rename obsolete pack, skipping it:\n%s"
1237
1793
                       % (e,))
1243
1799
                suffixes.append('.cix')
1244
1800
            for suffix in suffixes:
1245
1801
                try:
1246
 
                    self._index_transport.move(pack.name + suffix,
1247
 
                                               '../obsolete_packs/' + pack.name + suffix)
1248
 
                except (errors.PathError, errors.TransportError) as e:
 
1802
                    self._index_transport.rename(pack.name + suffix,
 
1803
                        '../obsolete_packs/' + pack.name + suffix)
 
1804
                except (errors.PathError, errors.TransportError), e:
1249
1805
                    mutter("couldn't rename obsolete index, skipping it:\n%s"
1250
1806
                           % (e,))
1251
1807
 
1281
1837
 
1282
1838
    def _remove_pack_indices(self, pack, ignore_missing=False):
1283
1839
        """Remove the indices for pack from the aggregated indices.
1284
 
 
 
1840
        
1285
1841
        :param ignore_missing: Suppress KeyErrors from calling remove_index.
1286
1842
        """
1287
 
        for index_type in Pack.index_definitions:
 
1843
        for index_type in Pack.index_definitions.keys():
1288
1844
            attr_name = index_type + '_index'
1289
1845
            aggregate_index = getattr(self, attr_name)
1290
1846
            if aggregate_index is not None:
1332
1888
        # load the disk nodes across
1333
1889
        disk_nodes = set()
1334
1890
        for index, key, value in self._iter_disk_pack_index():
1335
 
            disk_nodes.add((key[0].decode('ascii'), value))
 
1891
            disk_nodes.add((key, value))
1336
1892
        orig_disk_nodes = set(disk_nodes)
1337
1893
 
1338
1894
        # do a two-way diff against our original content
1339
1895
        current_nodes = set()
1340
 
        for name, sizes in viewitems(self._names):
 
1896
        for name, sizes in self._names.iteritems():
1341
1897
            current_nodes.add(
1342
 
                (name, b' '.join(b'%d' % size for size in sizes)))
 
1898
                ((name, ), ' '.join(str(size) for size in sizes)))
1343
1899
 
1344
1900
        # Packs no longer present in the repository, which were present when we
1345
1901
        # locked the repository
1369
1925
        new_names = dict(disk_nodes)
1370
1926
        # drop no longer present nodes
1371
1927
        for pack in self.all_packs():
1372
 
            if pack.name not in new_names:
 
1928
            if (pack.name,) not in new_names:
1373
1929
                removed.append(pack.name)
1374
1930
                self._remove_pack_from_memory(pack)
1375
1931
        # add new nodes/refresh existing ones
1376
 
        for name, value in disk_nodes:
 
1932
        for key, value in disk_nodes:
 
1933
            name = key[0]
1377
1934
            sizes = self._parse_index_sizes(value)
1378
1935
            if name in self._names:
1379
1936
                # existing
1385
1942
                    # disk index because the set values are the same, unless
1386
1943
                    # the only index shows up as deleted by the set difference
1387
1944
                    # - which it may. Until there is a specific test for this,
1388
 
                    # assume it's broken. RBC 20071017.
 
1945
                    # assume its broken. RBC 20071017.
1389
1946
                    self._remove_pack_from_memory(self.get_pack_by_name(name))
1390
1947
                    self._names[name] = sizes
1391
1948
                    self.get_pack_by_name(name)
1420
1977
            # TODO: handle same-name, index-size-changes here -
1421
1978
            # e.g. use the value from disk, not ours, *unless* we're the one
1422
1979
            # changing it.
1423
 
            for name, value in disk_nodes:
1424
 
                builder.add_node((name.encode('ascii'), ), value)
 
1980
            for key, value in disk_nodes:
 
1981
                builder.add_node(key, value)
1425
1982
            self.transport.put_file('pack-names', builder.finish(),
1426
 
                                    mode=self.repo.controldir._get_file_mode())
 
1983
                mode=self.repo.bzrdir._get_file_mode())
1427
1984
            self._packs_at_load = disk_nodes
1428
1985
            if clear_obsolete_packs:
1429
1986
                to_preserve = None
1430
1987
                if obsolete_packs:
1431
 
                    to_preserve = {o.name for o in obsolete_packs}
 
1988
                    to_preserve = set([o.name for o in obsolete_packs])
1432
1989
                already_obsolete = self._clear_obsolete_packs(to_preserve)
1433
1990
        finally:
1434
1991
            self._unlock_names()
1443
2000
            obsolete_packs = [o for o in obsolete_packs
1444
2001
                              if o.name not in already_obsolete]
1445
2002
            self._obsolete_packs(obsolete_packs)
1446
 
        return [new_node[0] for new_node in new_nodes]
 
2003
        return [new_node[0][0] for new_node in new_nodes]
1447
2004
 
1448
2005
    def reload_pack_names(self):
1449
2006
        """Sync our pack listing with what is present in the repository.
1455
2012
        :return: True if the in-memory list of packs has been altered at all.
1456
2013
        """
1457
2014
        # The ensure_loaded call is to handle the case where the first call
1458
 
        # made involving the collection was to reload_pack_names, where we
1459
 
        # don't have a view of disk contents. It's a bit of a bandaid, and
1460
 
        # causes two reads of pack-names, but it's a rare corner case not
1461
 
        # struck with regular push/pull etc.
 
2015
        # made involving the collection was to reload_pack_names, where we 
 
2016
        # don't have a view of disk contents. Its a bit of a bandaid, and
 
2017
        # causes two reads of pack-names, but its a rare corner case not struck
 
2018
        # with regular push/pull etc.
1462
2019
        first_read = self.ensure_loaded()
1463
2020
        if first_read:
1464
2021
            return True
1483
2040
            raise
1484
2041
        raise errors.RetryAutopack(self.repo, False, sys.exc_info())
1485
2042
 
1486
 
    def _restart_pack_operations(self):
1487
 
        """Reload the pack names list, and restart the autopack code."""
1488
 
        if not self.reload_pack_names():
1489
 
            # Re-raise the original exception, because something went missing
1490
 
            # and a restart didn't find it
1491
 
            raise
1492
 
        raise RetryPackOperations(self.repo, False, sys.exc_info())
1493
 
 
1494
2043
    def _clear_obsolete_packs(self, preserve=None):
1495
2044
        """Delete everything from the obsolete-packs directory.
1496
2045
 
1501
2050
        obsolete_pack_transport = self.transport.clone('obsolete_packs')
1502
2051
        if preserve is None:
1503
2052
            preserve = set()
1504
 
        try:
1505
 
            obsolete_pack_files = obsolete_pack_transport.list_dir('.')
1506
 
        except errors.NoSuchFile:
1507
 
            return found
1508
 
        for filename in obsolete_pack_files:
 
2053
        for filename in obsolete_pack_transport.list_dir('.'):
1509
2054
            name, ext = osutils.splitext(filename)
1510
2055
            if ext == '.pack':
1511
2056
                found.append(name)
1513
2058
                continue
1514
2059
            try:
1515
2060
                obsolete_pack_transport.delete(filename)
1516
 
            except (errors.PathError, errors.TransportError) as e:
 
2061
            except (errors.PathError, errors.TransportError), e:
1517
2062
                warning("couldn't delete obsolete pack, skipping it:\n%s"
1518
2063
                        % (e,))
1519
2064
        return found
1523
2068
        if not self.repo.is_write_locked():
1524
2069
            raise errors.NotWriteLocked(self)
1525
2070
        self._new_pack = self.pack_factory(self, upload_suffix='.pack',
1526
 
                                           file_mode=self.repo.controldir._get_file_mode())
 
2071
            file_mode=self.repo.bzrdir._get_file_mode())
1527
2072
        # allow writing: queue writes to a new index
1528
2073
        self.revision_index.add_writable_index(self._new_pack.revision_index,
1529
 
                                               self._new_pack)
 
2074
            self._new_pack)
1530
2075
        self.inventory_index.add_writable_index(self._new_pack.inventory_index,
1531
 
                                                self._new_pack)
 
2076
            self._new_pack)
1532
2077
        self.text_index.add_writable_index(self._new_pack.text_index,
1533
 
                                           self._new_pack)
 
2078
            self._new_pack)
1534
2079
        self._new_pack.text_index.set_optimize(combine_backing_indices=False)
1535
2080
        self.signature_index.add_writable_index(self._new_pack.signature_index,
1536
 
                                                self._new_pack)
 
2081
            self._new_pack)
1537
2082
        if self.chk_index is not None:
1538
2083
            self.chk_index.add_writable_index(self._new_pack.chk_index,
1539
 
                                              self._new_pack)
 
2084
                self._new_pack)
1540
2085
            self.repo.chk_bytes._index._add_callback = self.chk_index.add_callback
1541
 
            self._new_pack.chk_index.set_optimize(
1542
 
                combine_backing_indices=False)
 
2086
            self._new_pack.chk_index.set_optimize(combine_backing_indices=False)
1543
2087
 
1544
2088
        self.repo.inventories._index._add_callback = self.inventory_index.add_callback
1545
2089
        self.repo.revisions._index._add_callback = self.revision_index.add_callback
1550
2094
        # FIXME: just drop the transient index.
1551
2095
        # forget what names there are
1552
2096
        if self._new_pack is not None:
1553
 
            with cleanup.ExitStack() as stack:
1554
 
                stack.callback(setattr, self, '_new_pack', None)
1555
 
                # If we aborted while in the middle of finishing the write
1556
 
                # group, _remove_pack_indices could fail because the indexes are
1557
 
                # already gone.  But they're not there we shouldn't fail in this
1558
 
                # case, so we pass ignore_missing=True.
1559
 
                stack.callback(self._remove_pack_indices, self._new_pack,
1560
 
                               ignore_missing=True)
1561
 
                self._new_pack.abort()
 
2097
            operation = cleanup.OperationWithCleanups(self._new_pack.abort)
 
2098
            operation.add_cleanup(setattr, self, '_new_pack', None)
 
2099
            # If we aborted while in the middle of finishing the write
 
2100
            # group, _remove_pack_indices could fail because the indexes are
 
2101
            # already gone.  But they're not there we shouldn't fail in this
 
2102
            # case, so we pass ignore_missing=True.
 
2103
            operation.add_cleanup(self._remove_pack_indices, self._new_pack,
 
2104
                ignore_missing=True)
 
2105
            operation.run_simple()
1562
2106
        for resumed_pack in self._resumed_packs:
1563
 
            with cleanup.ExitStack() as stack:
1564
 
                # See comment in previous finally block.
1565
 
                stack.callback(self._remove_pack_indices, resumed_pack,
1566
 
                               ignore_missing=True)
1567
 
                resumed_pack.abort()
 
2107
            operation = cleanup.OperationWithCleanups(resumed_pack.abort)
 
2108
            # See comment in previous finally block.
 
2109
            operation.add_cleanup(self._remove_pack_indices, resumed_pack,
 
2110
                ignore_missing=True)
 
2111
            operation.run_simple()
1568
2112
        del self._resumed_packs[:]
1569
2113
 
1570
2114
    def _remove_resumed_pack_indices(self):
1581
2125
        # The base implementation does no checks.  GCRepositoryPackCollection
1582
2126
        # overrides this.
1583
2127
        return []
1584
 
 
 
2128
        
1585
2129
    def _commit_write_group(self):
1586
2130
        all_missing = set()
1587
2131
        for prefix, versioned_file in (
1595
2139
        if all_missing:
1596
2140
            raise errors.BzrCheckError(
1597
2141
                "Repository %s has missing compression parent(s) %r "
1598
 
                % (self.repo, sorted(all_missing)))
 
2142
                 % (self.repo, sorted(all_missing)))
1599
2143
        problems = self._check_new_inventories()
1600
2144
        if problems:
1601
2145
            problems_summary = '\n'.join(problems)
1649
2193
            self._resume_pack(token)
1650
2194
 
1651
2195
 
1652
 
class PackRepository(MetaDirVersionedFileRepository):
 
2196
class KnitPackRepository(KnitRepository):
1653
2197
    """Repository with knit objects stored inside pack containers.
1654
2198
 
1655
2199
    The layering for a KnitPackRepository is:
1658
2202
    ===================================================
1659
2203
    Tuple based apis below, string based, and key based apis above
1660
2204
    ---------------------------------------------------
1661
 
    VersionedFiles
 
2205
    KnitVersionedFiles
1662
2206
      Provides .texts, .revisions etc
1663
2207
      This adapts the N-tuple keys to physical knit records which only have a
1664
2208
      single string identifier (for historical reasons), which in older formats
1674
2218
 
1675
2219
    """
1676
2220
 
1677
 
    # These attributes are inherited from the Repository base class. Setting
1678
 
    # them to None ensures that if the constructor is changed to not initialize
1679
 
    # them, or a subclass fails to call the constructor, that an error will
1680
 
    # occur rather than the system working but generating incorrect data.
1681
 
    _commit_builder_class = None
1682
 
    _serializer = None
1683
 
 
1684
 
    def __init__(self, _format, a_controldir, control_files, _commit_builder_class,
1685
 
                 _serializer):
1686
 
        MetaDirRepository.__init__(self, _format, a_controldir, control_files)
1687
 
        self._commit_builder_class = _commit_builder_class
1688
 
        self._serializer = _serializer
 
2221
    def __init__(self, _format, a_bzrdir, control_files, _commit_builder_class,
 
2222
        _serializer):
 
2223
        KnitRepository.__init__(self, _format, a_bzrdir, control_files,
 
2224
            _commit_builder_class, _serializer)
 
2225
        index_transport = self._transport.clone('indices')
 
2226
        self._pack_collection = RepositoryPackCollection(self, self._transport,
 
2227
            index_transport,
 
2228
            self._transport.clone('upload'),
 
2229
            self._transport.clone('packs'),
 
2230
            _format.index_builder_class,
 
2231
            _format.index_class,
 
2232
            use_chk_index=self._format.supports_chks,
 
2233
            )
 
2234
        self.inventories = KnitVersionedFiles(
 
2235
            _KnitGraphIndex(self._pack_collection.inventory_index.combined_index,
 
2236
                add_callback=self._pack_collection.inventory_index.add_callback,
 
2237
                deltas=True, parents=True, is_locked=self.is_locked),
 
2238
            data_access=self._pack_collection.inventory_index.data_access,
 
2239
            max_delta_chain=200)
 
2240
        self.revisions = KnitVersionedFiles(
 
2241
            _KnitGraphIndex(self._pack_collection.revision_index.combined_index,
 
2242
                add_callback=self._pack_collection.revision_index.add_callback,
 
2243
                deltas=False, parents=True, is_locked=self.is_locked,
 
2244
                track_external_parent_refs=True),
 
2245
            data_access=self._pack_collection.revision_index.data_access,
 
2246
            max_delta_chain=0)
 
2247
        self.signatures = KnitVersionedFiles(
 
2248
            _KnitGraphIndex(self._pack_collection.signature_index.combined_index,
 
2249
                add_callback=self._pack_collection.signature_index.add_callback,
 
2250
                deltas=False, parents=False, is_locked=self.is_locked),
 
2251
            data_access=self._pack_collection.signature_index.data_access,
 
2252
            max_delta_chain=0)
 
2253
        self.texts = KnitVersionedFiles(
 
2254
            _KnitGraphIndex(self._pack_collection.text_index.combined_index,
 
2255
                add_callback=self._pack_collection.text_index.add_callback,
 
2256
                deltas=True, parents=True, is_locked=self.is_locked),
 
2257
            data_access=self._pack_collection.text_index.data_access,
 
2258
            max_delta_chain=200)
 
2259
        if _format.supports_chks:
 
2260
            # No graph, no compression:- references from chks are between
 
2261
            # different objects not temporal versions of the same; and without
 
2262
            # some sort of temporal structure knit compression will just fail.
 
2263
            self.chk_bytes = KnitVersionedFiles(
 
2264
                _KnitGraphIndex(self._pack_collection.chk_index.combined_index,
 
2265
                    add_callback=self._pack_collection.chk_index.add_callback,
 
2266
                    deltas=False, parents=False, is_locked=self.is_locked),
 
2267
                data_access=self._pack_collection.chk_index.data_access,
 
2268
                max_delta_chain=0)
 
2269
        else:
 
2270
            self.chk_bytes = None
 
2271
        # True when the repository object is 'write locked' (as opposed to the
 
2272
        # physical lock only taken out around changes to the pack-names list.)
 
2273
        # Another way to represent this would be a decorator around the control
 
2274
        # files object that presents logical locks as physical ones - if this
 
2275
        # gets ugly consider that alternative design. RBC 20071011
 
2276
        self._write_lock_count = 0
 
2277
        self._transaction = None
 
2278
        # for tests
 
2279
        self._reconcile_does_inventory_gc = True
1689
2280
        self._reconcile_fixes_text_parents = True
1690
 
        if self._format.supports_external_lookups:
1691
 
            self._unstacked_provider = graph.CachingParentsProvider(
1692
 
                self._make_parents_provider_unstacked())
1693
 
        else:
1694
 
            self._unstacked_provider = graph.CachingParentsProvider(self)
1695
 
        self._unstacked_provider.disable_cache()
 
2281
        self._reconcile_backsup_inventory = False
1696
2282
 
1697
 
    def _all_revision_ids(self):
1698
 
        """See Repository.all_revision_ids()."""
1699
 
        with self.lock_read():
1700
 
            return [key[0] for key in self.revisions.keys()]
 
2283
    def _warn_if_deprecated(self, branch=None):
 
2284
        # This class isn't deprecated, but one sub-format is
 
2285
        if isinstance(self._format, RepositoryFormatKnitPack5RichRootBroken):
 
2286
            super(KnitPackRepository, self)._warn_if_deprecated(branch)
1701
2287
 
1702
2288
    def _abort_write_group(self):
1703
2289
        self.revisions._index._key_dependencies.clear()
1704
2290
        self._pack_collection._abort_write_group()
1705
2291
 
 
2292
    def _get_source(self, to_format):
 
2293
        if to_format.network_name() == self._format.network_name():
 
2294
            return KnitPackStreamSource(self, to_format)
 
2295
        return super(KnitPackRepository, self)._get_source(to_format)
 
2296
 
1706
2297
    def _make_parents_provider(self):
1707
 
        if not self._format.supports_external_lookups:
1708
 
            return self._unstacked_provider
1709
 
        return graph.StackedParentsProvider(_LazyListJoin(
1710
 
            [self._unstacked_provider], self._fallback_repositories))
 
2298
        return graph.CachingParentsProvider(self)
1711
2299
 
1712
2300
    def _refresh_data(self):
1713
2301
        if not self.is_locked():
1714
2302
            return
1715
2303
        self._pack_collection.reload_pack_names()
1716
 
        self._unstacked_provider.disable_cache()
1717
 
        self._unstacked_provider.enable_cache()
1718
2304
 
1719
2305
    def _start_write_group(self):
1720
2306
        self._pack_collection._start_write_group()
1722
2308
    def _commit_write_group(self):
1723
2309
        hint = self._pack_collection._commit_write_group()
1724
2310
        self.revisions._index._key_dependencies.clear()
1725
 
        # The commit may have added keys that were previously cached as
1726
 
        # missing, so reset the cache.
1727
 
        self._unstacked_provider.disable_cache()
1728
 
        self._unstacked_provider.enable_cache()
1729
2311
        return hint
1730
2312
 
1731
2313
    def suspend_write_group(self):
1758
2340
        return self._write_lock_count
1759
2341
 
1760
2342
    def lock_write(self, token=None):
1761
 
        """Lock the repository for writes.
1762
 
 
1763
 
        :return: A breezy.repository.RepositoryWriteLockResult.
1764
 
        """
1765
2343
        locked = self.is_locked()
1766
2344
        if not self._write_lock_count and locked:
1767
2345
            raise errors.ReadOnlyError(self)
1772
2350
            if 'relock' in debug.debug_flags and self._prev_lock == 'w':
1773
2351
                note('%r was write locked again', self)
1774
2352
            self._prev_lock = 'w'
1775
 
            self._unstacked_provider.enable_cache()
1776
2353
            for repo in self._fallback_repositories:
1777
2354
                # Writes don't affect fallback repos
1778
2355
                repo.lock_read()
1779
2356
            self._refresh_data()
1780
 
        return RepositoryWriteLockResult(self.unlock, None)
1781
2357
 
1782
2358
    def lock_read(self):
1783
 
        """Lock the repository for reads.
1784
 
 
1785
 
        :return: A breezy.lock.LogicalLockResult.
1786
 
        """
1787
2359
        locked = self.is_locked()
1788
2360
        if self._write_lock_count:
1789
2361
            self._write_lock_count += 1
1793
2365
            if 'relock' in debug.debug_flags and self._prev_lock == 'r':
1794
2366
                note('%r was read locked again', self)
1795
2367
            self._prev_lock = 'r'
1796
 
            self._unstacked_provider.enable_cache()
1797
2368
            for repo in self._fallback_repositories:
1798
2369
                repo.lock_read()
1799
2370
            self._refresh_data()
1800
 
        return LogicalLockResult(self.unlock)
1801
2371
 
1802
2372
    def leave_lock_in_place(self):
1803
2373
        # not supported - raise an error
1807
2377
        # not supported - raise an error
1808
2378
        raise NotImplementedError(self.dont_leave_lock_in_place)
1809
2379
 
 
2380
    @needs_write_lock
1810
2381
    def pack(self, hint=None, clean_obsolete_packs=False):
1811
2382
        """Compress the data within the repository.
1812
2383
 
1813
2384
        This will pack all the data to a single pack. In future it may
1814
2385
        recompress deltas or do other such expensive operations.
1815
2386
        """
1816
 
        with self.lock_write():
1817
 
            self._pack_collection.pack(
1818
 
                hint=hint, clean_obsolete_packs=clean_obsolete_packs)
 
2387
        self._pack_collection.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1819
2388
 
 
2389
    @needs_write_lock
1820
2390
    def reconcile(self, other=None, thorough=False):
1821
2391
        """Reconcile this repository."""
1822
 
        from .reconcile import PackReconciler
1823
 
        with self.lock_write():
1824
 
            reconciler = PackReconciler(self, thorough=thorough)
1825
 
            return reconciler.reconcile()
 
2392
        from bzrlib.reconcile import PackReconciler
 
2393
        reconciler = PackReconciler(self, thorough=thorough)
 
2394
        reconciler.reconcile()
 
2395
        return reconciler
1826
2396
 
1827
2397
    def _reconcile_pack(self, collection, packs, extension, revs, pb):
1828
 
        raise NotImplementedError(self._reconcile_pack)
 
2398
        packer = ReconcilePacker(collection, packs, extension, revs)
 
2399
        return packer.pack(pb)
1829
2400
 
1830
2401
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1831
2402
    def unlock(self):
1832
2403
        if self._write_lock_count == 1 and self._write_group is not None:
1833
2404
            self.abort_write_group()
1834
 
            self._unstacked_provider.disable_cache()
1835
2405
            self._transaction = None
1836
2406
            self._write_lock_count = 0
1837
2407
            raise errors.BzrError(
1847
2417
            self.control_files.unlock()
1848
2418
 
1849
2419
        if not self.is_locked():
1850
 
            self._unstacked_provider.disable_cache()
1851
2420
            for repo in self._fallback_repositories:
1852
2421
                repo.unlock()
1853
2422
 
1854
2423
 
1855
 
class RepositoryFormatPack(MetaDirVersionedFileRepositoryFormat):
 
2424
class KnitPackStreamSource(StreamSource):
 
2425
    """A StreamSource used to transfer data between same-format KnitPack repos.
 
2426
 
 
2427
    This source assumes:
 
2428
        1) Same serialization format for all objects
 
2429
        2) Same root information
 
2430
        3) XML format inventories
 
2431
        4) Atomic inserts (so we can stream inventory texts before text
 
2432
           content)
 
2433
        5) No chk_bytes
 
2434
    """
 
2435
 
 
2436
    def __init__(self, from_repository, to_format):
 
2437
        super(KnitPackStreamSource, self).__init__(from_repository, to_format)
 
2438
        self._text_keys = None
 
2439
        self._text_fetch_order = 'unordered'
 
2440
 
 
2441
    def _get_filtered_inv_stream(self, revision_ids):
 
2442
        from_repo = self.from_repository
 
2443
        parent_ids = from_repo._find_parent_ids_of_revisions(revision_ids)
 
2444
        parent_keys = [(p,) for p in parent_ids]
 
2445
        find_text_keys = from_repo._find_text_key_references_from_xml_inventory_lines
 
2446
        parent_text_keys = set(find_text_keys(
 
2447
            from_repo._inventory_xml_lines_for_keys(parent_keys)))
 
2448
        content_text_keys = set()
 
2449
        knit = KnitVersionedFiles(None, None)
 
2450
        factory = KnitPlainFactory()
 
2451
        def find_text_keys_from_content(record):
 
2452
            if record.storage_kind not in ('knit-delta-gz', 'knit-ft-gz'):
 
2453
                raise ValueError("Unknown content storage kind for"
 
2454
                    " inventory text: %s" % (record.storage_kind,))
 
2455
            # It's a knit record, it has a _raw_record field (even if it was
 
2456
            # reconstituted from a network stream).
 
2457
            raw_data = record._raw_record
 
2458
            # read the entire thing
 
2459
            revision_id = record.key[-1]
 
2460
            content, _ = knit._parse_record(revision_id, raw_data)
 
2461
            if record.storage_kind == 'knit-delta-gz':
 
2462
                line_iterator = factory.get_linedelta_content(content)
 
2463
            elif record.storage_kind == 'knit-ft-gz':
 
2464
                line_iterator = factory.get_fulltext_content(content)
 
2465
            content_text_keys.update(find_text_keys(
 
2466
                [(line, revision_id) for line in line_iterator]))
 
2467
        revision_keys = [(r,) for r in revision_ids]
 
2468
        def _filtered_inv_stream():
 
2469
            source_vf = from_repo.inventories
 
2470
            stream = source_vf.get_record_stream(revision_keys,
 
2471
                                                 'unordered', False)
 
2472
            for record in stream:
 
2473
                if record.storage_kind == 'absent':
 
2474
                    raise errors.NoSuchRevision(from_repo, record.key)
 
2475
                find_text_keys_from_content(record)
 
2476
                yield record
 
2477
            self._text_keys = content_text_keys - parent_text_keys
 
2478
        return ('inventories', _filtered_inv_stream())
 
2479
 
 
2480
    def _get_text_stream(self):
 
2481
        # Note: We know we don't have to handle adding root keys, because both
 
2482
        # the source and target are the identical network name.
 
2483
        text_stream = self.from_repository.texts.get_record_stream(
 
2484
                        self._text_keys, self._text_fetch_order, False)
 
2485
        return ('texts', text_stream)
 
2486
 
 
2487
    def get_stream(self, search):
 
2488
        revision_ids = search.get_keys()
 
2489
        for stream_info in self._fetch_revision_texts(revision_ids):
 
2490
            yield stream_info
 
2491
        self._revision_keys = [(rev_id,) for rev_id in revision_ids]
 
2492
        yield self._get_filtered_inv_stream(revision_ids)
 
2493
        yield self._get_text_stream()
 
2494
 
 
2495
 
 
2496
 
 
2497
class RepositoryFormatPack(MetaDirRepositoryFormat):
1856
2498
    """Format logic for pack structured repositories.
1857
2499
 
1858
2500
    This repository format has:
1888
2530
    index_class = None
1889
2531
    _fetch_uses_deltas = True
1890
2532
    fast_deltas = False
1891
 
    supports_funky_characters = True
1892
 
    revision_graph_can_have_wrong_parents = True
1893
2533
 
1894
 
    def initialize(self, a_controldir, shared=False):
 
2534
    def initialize(self, a_bzrdir, shared=False):
1895
2535
        """Create a pack based repository.
1896
2536
 
1897
 
        :param a_controldir: bzrdir to contain the new repository; must already
 
2537
        :param a_bzrdir: bzrdir to contain the new repository; must already
1898
2538
            be initialized.
1899
2539
        :param shared: If true the repository will be initialized as a shared
1900
2540
                       repository.
1901
2541
        """
1902
 
        mutter('creating repository in %s.', a_controldir.transport.base)
 
2542
        mutter('creating repository in %s.', a_bzrdir.transport.base)
1903
2543
        dirs = ['indices', 'obsolete_packs', 'packs', 'upload']
1904
2544
        builder = self.index_builder_class()
1905
2545
        files = [('pack-names', builder.finish())]
1906
2546
        utf8_files = [('format', self.get_format_string())]
1907
2547
 
1908
 
        self._upload_blank_content(
1909
 
            a_controldir, dirs, files, utf8_files, shared)
1910
 
        repository = self.open(a_controldir=a_controldir, _found=True)
1911
 
        self._run_post_repo_init_hooks(repository, a_controldir, shared)
 
2548
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
2549
        repository = self.open(a_bzrdir=a_bzrdir, _found=True)
 
2550
        self._run_post_repo_init_hooks(repository, a_bzrdir, shared)
1912
2551
        return repository
1913
2552
 
1914
 
    def open(self, a_controldir, _found=False, _override_transport=None):
 
2553
    def open(self, a_bzrdir, _found=False, _override_transport=None):
1915
2554
        """See RepositoryFormat.open().
1916
2555
 
1917
2556
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
1919
2558
                                    than normal. I.e. during 'upgrade'.
1920
2559
        """
1921
2560
        if not _found:
1922
 
            format = RepositoryFormatMetaDir.find_format(a_controldir)
 
2561
            format = RepositoryFormat.find_format(a_bzrdir)
1923
2562
        if _override_transport is not None:
1924
2563
            repo_transport = _override_transport
1925
2564
        else:
1926
 
            repo_transport = a_controldir.get_repository_transport(None)
 
2565
            repo_transport = a_bzrdir.get_repository_transport(None)
1927
2566
        control_files = lockable_files.LockableFiles(repo_transport,
1928
 
                                                     'lock', lockdir.LockDir)
 
2567
                                'lock', lockdir.LockDir)
1929
2568
        return self.repository_class(_format=self,
1930
 
                                     a_controldir=a_controldir,
1931
 
                                     control_files=control_files,
1932
 
                                     _commit_builder_class=self._commit_builder_class,
1933
 
                                     _serializer=self._serializer)
1934
 
 
1935
 
 
1936
 
class RetryPackOperations(errors.RetryWithNewPacks):
1937
 
    """Raised when we are packing and we find a missing file.
1938
 
 
1939
 
    Meant as a signaling exception, to tell the RepositoryPackCollection.pack
1940
 
    code it should try again.
1941
 
    """
1942
 
 
1943
 
    internal_error = True
1944
 
 
1945
 
    _fmt = ("Pack files have changed, reload and try pack again."
1946
 
            " context: %(context)s %(orig_error)s")
1947
 
 
1948
 
 
1949
 
class _DirectPackAccess(object):
1950
 
    """Access to data in one or more packs with less translation."""
1951
 
 
1952
 
    def __init__(self, index_to_packs, reload_func=None, flush_func=None):
1953
 
        """Create a _DirectPackAccess object.
1954
 
 
1955
 
        :param index_to_packs: A dict mapping index objects to the transport
1956
 
            and file names for obtaining data.
1957
 
        :param reload_func: A function to call if we determine that the pack
1958
 
            files have moved and we need to reload our caches. See
1959
 
            breezy.repo_fmt.pack_repo.AggregateIndex for more details.
1960
 
        """
1961
 
        self._container_writer = None
1962
 
        self._write_index = None
1963
 
        self._indices = index_to_packs
1964
 
        self._reload_func = reload_func
1965
 
        self._flush_func = flush_func
1966
 
 
1967
 
    def add_raw_record(self, key, size, raw_data):
1968
 
        """Add raw knit bytes to a storage area.
1969
 
 
1970
 
        The data is spooled to the container writer in one bytes-record per
1971
 
        raw data item.
1972
 
 
1973
 
        :param key: key of the data segment
1974
 
        :param size: length of the data segment
1975
 
        :param raw_data: A bytestring containing the data.
1976
 
        :return: An opaque index memo For _DirectPackAccess the memo is
1977
 
            (index, pos, length), where the index field is the write_index
1978
 
            object supplied to the PackAccess object.
1979
 
        """
1980
 
        p_offset, p_length = self._container_writer.add_bytes_record(
1981
 
            raw_data, size, [])
1982
 
        return (self._write_index, p_offset, p_length)
1983
 
 
1984
 
    def add_raw_records(self, key_sizes, raw_data):
1985
 
        """Add raw knit bytes to a storage area.
1986
 
 
1987
 
        The data is spooled to the container writer in one bytes-record per
1988
 
        raw data item.
1989
 
 
1990
 
        :param sizes: An iterable of tuples containing the key and size of each
1991
 
            raw data segment.
1992
 
        :param raw_data: A bytestring containing the data.
1993
 
        :return: A list of memos to retrieve the record later. Each memo is an
1994
 
            opaque index memo. For _DirectPackAccess the memo is (index, pos,
1995
 
            length), where the index field is the write_index object supplied
1996
 
            to the PackAccess object.
1997
 
        """
1998
 
        raw_data = b''.join(raw_data)
1999
 
        if not isinstance(raw_data, bytes):
2000
 
            raise AssertionError(
2001
 
                'data must be plain bytes was %s' % type(raw_data))
2002
 
        result = []
2003
 
        offset = 0
2004
 
        for key, size in key_sizes:
2005
 
            result.append(
2006
 
                self.add_raw_record(key, size, [raw_data[offset:offset + size]]))
2007
 
            offset += size
2008
 
        return result
2009
 
 
2010
 
    def flush(self):
2011
 
        """Flush pending writes on this access object.
2012
 
 
2013
 
        This will flush any buffered writes to a NewPack.
2014
 
        """
2015
 
        if self._flush_func is not None:
2016
 
            self._flush_func()
2017
 
 
2018
 
    def get_raw_records(self, memos_for_retrieval):
2019
 
        """Get the raw bytes for a records.
2020
 
 
2021
 
        :param memos_for_retrieval: An iterable containing the (index, pos,
2022
 
            length) memo for retrieving the bytes. The Pack access method
2023
 
            looks up the pack to use for a given record in its index_to_pack
2024
 
            map.
2025
 
        :return: An iterator over the bytes of the records.
2026
 
        """
2027
 
        # first pass, group into same-index requests
2028
 
        request_lists = []
2029
 
        current_index = None
2030
 
        for (index, offset, length) in memos_for_retrieval:
2031
 
            if current_index == index:
2032
 
                current_list.append((offset, length))
2033
 
            else:
2034
 
                if current_index is not None:
2035
 
                    request_lists.append((current_index, current_list))
2036
 
                current_index = index
2037
 
                current_list = [(offset, length)]
2038
 
        # handle the last entry
2039
 
        if current_index is not None:
2040
 
            request_lists.append((current_index, current_list))
2041
 
        for index, offsets in request_lists:
2042
 
            try:
2043
 
                transport, path = self._indices[index]
2044
 
            except KeyError:
2045
 
                # A KeyError here indicates that someone has triggered an index
2046
 
                # reload, and this index has gone missing, we need to start
2047
 
                # over.
2048
 
                if self._reload_func is None:
2049
 
                    # If we don't have a _reload_func there is nothing that can
2050
 
                    # be done
2051
 
                    raise
2052
 
                raise errors.RetryWithNewPacks(index,
2053
 
                                               reload_occurred=True,
2054
 
                                               exc_info=sys.exc_info())
2055
 
            try:
2056
 
                reader = pack.make_readv_reader(transport, path, offsets)
2057
 
                for names, read_func in reader.iter_records():
2058
 
                    yield read_func(None)
2059
 
            except errors.NoSuchFile:
2060
 
                # A NoSuchFile error indicates that a pack file has gone
2061
 
                # missing on disk, we need to trigger a reload, and start over.
2062
 
                if self._reload_func is None:
2063
 
                    raise
2064
 
                raise errors.RetryWithNewPacks(transport.abspath(path),
2065
 
                                               reload_occurred=False,
2066
 
                                               exc_info=sys.exc_info())
2067
 
 
2068
 
    def set_writer(self, writer, index, transport_packname):
2069
 
        """Set a writer to use for adding data."""
2070
 
        if index is not None:
2071
 
            self._indices[index] = transport_packname
2072
 
        self._container_writer = writer
2073
 
        self._write_index = index
2074
 
 
2075
 
    def reload_or_raise(self, retry_exc):
2076
 
        """Try calling the reload function, or re-raise the original exception.
2077
 
 
2078
 
        This should be called after _DirectPackAccess raises a
2079
 
        RetryWithNewPacks exception. This function will handle the common logic
2080
 
        of determining when the error is fatal versus being temporary.
2081
 
        It will also make sure that the original exception is raised, rather
2082
 
        than the RetryWithNewPacks exception.
2083
 
 
2084
 
        If this function returns, then the calling function should retry
2085
 
        whatever operation was being performed. Otherwise an exception will
2086
 
        be raised.
2087
 
 
2088
 
        :param retry_exc: A RetryWithNewPacks exception.
2089
 
        """
2090
 
        is_error = False
2091
 
        if self._reload_func is None:
2092
 
            is_error = True
2093
 
        elif not self._reload_func():
2094
 
            # The reload claimed that nothing changed
2095
 
            if not retry_exc.reload_occurred:
2096
 
                # If there wasn't an earlier reload, then we really were
2097
 
                # expecting to find changes. We didn't find them, so this is a
2098
 
                # hard error
2099
 
                is_error = True
2100
 
        if is_error:
2101
 
            # GZ 2017-03-27: No real reason this needs the original traceback.
2102
 
            reraise(*retry_exc.exc_info)
 
2569
                              a_bzrdir=a_bzrdir,
 
2570
                              control_files=control_files,
 
2571
                              _commit_builder_class=self._commit_builder_class,
 
2572
                              _serializer=self._serializer)
 
2573
 
 
2574
 
 
2575
class RepositoryFormatKnitPack1(RepositoryFormatPack):
 
2576
    """A no-subtrees parameterized Pack repository.
 
2577
 
 
2578
    This format was introduced in 0.92.
 
2579
    """
 
2580
 
 
2581
    repository_class = KnitPackRepository
 
2582
    _commit_builder_class = PackCommitBuilder
 
2583
    @property
 
2584
    def _serializer(self):
 
2585
        return xml5.serializer_v5
 
2586
    # What index classes to use
 
2587
    index_builder_class = InMemoryGraphIndex
 
2588
    index_class = GraphIndex
 
2589
 
 
2590
    def _get_matching_bzrdir(self):
 
2591
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
 
2592
 
 
2593
    def _ignore_setting_bzrdir(self, format):
 
2594
        pass
 
2595
 
 
2596
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2597
 
 
2598
    def get_format_string(self):
 
2599
        """See RepositoryFormat.get_format_string()."""
 
2600
        return "Bazaar pack repository format 1 (needs bzr 0.92)\n"
 
2601
 
 
2602
    def get_format_description(self):
 
2603
        """See RepositoryFormat.get_format_description()."""
 
2604
        return "Packs containing knits without subtree support"
 
2605
 
 
2606
 
 
2607
class RepositoryFormatKnitPack3(RepositoryFormatPack):
 
2608
    """A subtrees parameterized Pack repository.
 
2609
 
 
2610
    This repository format uses the xml7 serializer to get:
 
2611
     - support for recording full info about the tree root
 
2612
     - support for recording tree-references
 
2613
 
 
2614
    This format was introduced in 0.92.
 
2615
    """
 
2616
 
 
2617
    repository_class = KnitPackRepository
 
2618
    _commit_builder_class = PackRootCommitBuilder
 
2619
    rich_root_data = True
 
2620
    experimental = True
 
2621
    supports_tree_reference = True
 
2622
    @property
 
2623
    def _serializer(self):
 
2624
        return xml7.serializer_v7
 
2625
    # What index classes to use
 
2626
    index_builder_class = InMemoryGraphIndex
 
2627
    index_class = GraphIndex
 
2628
 
 
2629
    def _get_matching_bzrdir(self):
 
2630
        return bzrdir.format_registry.make_bzrdir(
 
2631
            'pack-0.92-subtree')
 
2632
 
 
2633
    def _ignore_setting_bzrdir(self, format):
 
2634
        pass
 
2635
 
 
2636
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2637
 
 
2638
    def get_format_string(self):
 
2639
        """See RepositoryFormat.get_format_string()."""
 
2640
        return "Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n"
 
2641
 
 
2642
    def get_format_description(self):
 
2643
        """See RepositoryFormat.get_format_description()."""
 
2644
        return "Packs containing knits with subtree support\n"
 
2645
 
 
2646
 
 
2647
class RepositoryFormatKnitPack4(RepositoryFormatPack):
 
2648
    """A rich-root, no subtrees parameterized Pack repository.
 
2649
 
 
2650
    This repository format uses the xml6 serializer to get:
 
2651
     - support for recording full info about the tree root
 
2652
 
 
2653
    This format was introduced in 1.0.
 
2654
    """
 
2655
 
 
2656
    repository_class = KnitPackRepository
 
2657
    _commit_builder_class = PackRootCommitBuilder
 
2658
    rich_root_data = True
 
2659
    supports_tree_reference = False
 
2660
    @property
 
2661
    def _serializer(self):
 
2662
        return xml6.serializer_v6
 
2663
    # What index classes to use
 
2664
    index_builder_class = InMemoryGraphIndex
 
2665
    index_class = GraphIndex
 
2666
 
 
2667
    def _get_matching_bzrdir(self):
 
2668
        return bzrdir.format_registry.make_bzrdir(
 
2669
            'rich-root-pack')
 
2670
 
 
2671
    def _ignore_setting_bzrdir(self, format):
 
2672
        pass
 
2673
 
 
2674
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2675
 
 
2676
    def get_format_string(self):
 
2677
        """See RepositoryFormat.get_format_string()."""
 
2678
        return ("Bazaar pack repository format 1 with rich root"
 
2679
                " (needs bzr 1.0)\n")
 
2680
 
 
2681
    def get_format_description(self):
 
2682
        """See RepositoryFormat.get_format_description()."""
 
2683
        return "Packs containing knits with rich root support\n"
 
2684
 
 
2685
 
 
2686
class RepositoryFormatKnitPack5(RepositoryFormatPack):
 
2687
    """Repository that supports external references to allow stacking.
 
2688
 
 
2689
    New in release 1.6.
 
2690
 
 
2691
    Supports external lookups, which results in non-truncated ghosts after
 
2692
    reconcile compared to pack-0.92 formats.
 
2693
    """
 
2694
 
 
2695
    repository_class = KnitPackRepository
 
2696
    _commit_builder_class = PackCommitBuilder
 
2697
    supports_external_lookups = True
 
2698
    # What index classes to use
 
2699
    index_builder_class = InMemoryGraphIndex
 
2700
    index_class = GraphIndex
 
2701
 
 
2702
    @property
 
2703
    def _serializer(self):
 
2704
        return xml5.serializer_v5
 
2705
 
 
2706
    def _get_matching_bzrdir(self):
 
2707
        return bzrdir.format_registry.make_bzrdir('1.6')
 
2708
 
 
2709
    def _ignore_setting_bzrdir(self, format):
 
2710
        pass
 
2711
 
 
2712
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2713
 
 
2714
    def get_format_string(self):
 
2715
        """See RepositoryFormat.get_format_string()."""
 
2716
        return "Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n"
 
2717
 
 
2718
    def get_format_description(self):
 
2719
        """See RepositoryFormat.get_format_description()."""
 
2720
        return "Packs 5 (adds stacking support, requires bzr 1.6)"
 
2721
 
 
2722
 
 
2723
class RepositoryFormatKnitPack5RichRoot(RepositoryFormatPack):
 
2724
    """A repository with rich roots and stacking.
 
2725
 
 
2726
    New in release 1.6.1.
 
2727
 
 
2728
    Supports stacking on other repositories, allowing data to be accessed
 
2729
    without being stored locally.
 
2730
    """
 
2731
 
 
2732
    repository_class = KnitPackRepository
 
2733
    _commit_builder_class = PackRootCommitBuilder
 
2734
    rich_root_data = True
 
2735
    supports_tree_reference = False # no subtrees
 
2736
    supports_external_lookups = True
 
2737
    # What index classes to use
 
2738
    index_builder_class = InMemoryGraphIndex
 
2739
    index_class = GraphIndex
 
2740
 
 
2741
    @property
 
2742
    def _serializer(self):
 
2743
        return xml6.serializer_v6
 
2744
 
 
2745
    def _get_matching_bzrdir(self):
 
2746
        return bzrdir.format_registry.make_bzrdir(
 
2747
            '1.6.1-rich-root')
 
2748
 
 
2749
    def _ignore_setting_bzrdir(self, format):
 
2750
        pass
 
2751
 
 
2752
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2753
 
 
2754
    def get_format_string(self):
 
2755
        """See RepositoryFormat.get_format_string()."""
 
2756
        return "Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n"
 
2757
 
 
2758
    def get_format_description(self):
 
2759
        return "Packs 5 rich-root (adds stacking support, requires bzr 1.6.1)"
 
2760
 
 
2761
 
 
2762
class RepositoryFormatKnitPack5RichRootBroken(RepositoryFormatPack):
 
2763
    """A repository with rich roots and external references.
 
2764
 
 
2765
    New in release 1.6.
 
2766
 
 
2767
    Supports external lookups, which results in non-truncated ghosts after
 
2768
    reconcile compared to pack-0.92 formats.
 
2769
 
 
2770
    This format was deprecated because the serializer it uses accidentally
 
2771
    supported subtrees, when the format was not intended to. This meant that
 
2772
    someone could accidentally fetch from an incorrect repository.
 
2773
    """
 
2774
 
 
2775
    repository_class = KnitPackRepository
 
2776
    _commit_builder_class = PackRootCommitBuilder
 
2777
    rich_root_data = True
 
2778
    supports_tree_reference = False # no subtrees
 
2779
 
 
2780
    supports_external_lookups = True
 
2781
    # What index classes to use
 
2782
    index_builder_class = InMemoryGraphIndex
 
2783
    index_class = GraphIndex
 
2784
 
 
2785
    @property
 
2786
    def _serializer(self):
 
2787
        return xml7.serializer_v7
 
2788
 
 
2789
    def _get_matching_bzrdir(self):
 
2790
        matching = bzrdir.format_registry.make_bzrdir(
 
2791
            '1.6.1-rich-root')
 
2792
        matching.repository_format = self
 
2793
        return matching
 
2794
 
 
2795
    def _ignore_setting_bzrdir(self, format):
 
2796
        pass
 
2797
 
 
2798
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2799
 
 
2800
    def get_format_string(self):
 
2801
        """See RepositoryFormat.get_format_string()."""
 
2802
        return "Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n"
 
2803
 
 
2804
    def get_format_description(self):
 
2805
        return ("Packs 5 rich-root (adds stacking support, requires bzr 1.6)"
 
2806
                " (deprecated)")
 
2807
 
 
2808
 
 
2809
class RepositoryFormatKnitPack6(RepositoryFormatPack):
 
2810
    """A repository with stacking and btree indexes,
 
2811
    without rich roots or subtrees.
 
2812
 
 
2813
    This is equivalent to pack-1.6 with B+Tree indices.
 
2814
    """
 
2815
 
 
2816
    repository_class = KnitPackRepository
 
2817
    _commit_builder_class = PackCommitBuilder
 
2818
    supports_external_lookups = True
 
2819
    # What index classes to use
 
2820
    index_builder_class = BTreeBuilder
 
2821
    index_class = BTreeGraphIndex
 
2822
 
 
2823
    @property
 
2824
    def _serializer(self):
 
2825
        return xml5.serializer_v5
 
2826
 
 
2827
    def _get_matching_bzrdir(self):
 
2828
        return bzrdir.format_registry.make_bzrdir('1.9')
 
2829
 
 
2830
    def _ignore_setting_bzrdir(self, format):
 
2831
        pass
 
2832
 
 
2833
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2834
 
 
2835
    def get_format_string(self):
 
2836
        """See RepositoryFormat.get_format_string()."""
 
2837
        return "Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n"
 
2838
 
 
2839
    def get_format_description(self):
 
2840
        """See RepositoryFormat.get_format_description()."""
 
2841
        return "Packs 6 (uses btree indexes, requires bzr 1.9)"
 
2842
 
 
2843
 
 
2844
class RepositoryFormatKnitPack6RichRoot(RepositoryFormatPack):
 
2845
    """A repository with rich roots, no subtrees, stacking and btree indexes.
 
2846
 
 
2847
    1.6-rich-root with B+Tree indices.
 
2848
    """
 
2849
 
 
2850
    repository_class = KnitPackRepository
 
2851
    _commit_builder_class = PackRootCommitBuilder
 
2852
    rich_root_data = True
 
2853
    supports_tree_reference = False # no subtrees
 
2854
    supports_external_lookups = True
 
2855
    # What index classes to use
 
2856
    index_builder_class = BTreeBuilder
 
2857
    index_class = BTreeGraphIndex
 
2858
 
 
2859
    @property
 
2860
    def _serializer(self):
 
2861
        return xml6.serializer_v6
 
2862
 
 
2863
    def _get_matching_bzrdir(self):
 
2864
        return bzrdir.format_registry.make_bzrdir(
 
2865
            '1.9-rich-root')
 
2866
 
 
2867
    def _ignore_setting_bzrdir(self, format):
 
2868
        pass
 
2869
 
 
2870
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2871
 
 
2872
    def get_format_string(self):
 
2873
        """See RepositoryFormat.get_format_string()."""
 
2874
        return "Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n"
 
2875
 
 
2876
    def get_format_description(self):
 
2877
        return "Packs 6 rich-root (uses btree indexes, requires bzr 1.9)"
 
2878
 
 
2879
 
 
2880
class RepositoryFormatPackDevelopment2Subtree(RepositoryFormatPack):
 
2881
    """A subtrees development repository.
 
2882
 
 
2883
    This format should be retained until the second release after bzr 1.7.
 
2884
 
 
2885
    1.6.1-subtree[as it might have been] with B+Tree indices.
 
2886
 
 
2887
    This is [now] retained until we have a CHK based subtree format in
 
2888
    development.
 
2889
    """
 
2890
 
 
2891
    repository_class = KnitPackRepository
 
2892
    _commit_builder_class = PackRootCommitBuilder
 
2893
    rich_root_data = True
 
2894
    experimental = True
 
2895
    supports_tree_reference = True
 
2896
    supports_external_lookups = True
 
2897
    # What index classes to use
 
2898
    index_builder_class = BTreeBuilder
 
2899
    index_class = BTreeGraphIndex
 
2900
 
 
2901
    @property
 
2902
    def _serializer(self):
 
2903
        return xml7.serializer_v7
 
2904
 
 
2905
    def _get_matching_bzrdir(self):
 
2906
        return bzrdir.format_registry.make_bzrdir(
 
2907
            'development-subtree')
 
2908
 
 
2909
    def _ignore_setting_bzrdir(self, format):
 
2910
        pass
 
2911
 
 
2912
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2913
 
 
2914
    def get_format_string(self):
 
2915
        """See RepositoryFormat.get_format_string()."""
 
2916
        return ("Bazaar development format 2 with subtree support "
 
2917
            "(needs bzr.dev from before 1.8)\n")
 
2918
 
 
2919
    def get_format_description(self):
 
2920
        """See RepositoryFormat.get_format_description()."""
 
2921
        return ("Development repository format, currently the same as "
 
2922
            "1.6.1-subtree with B+Tree indices.\n")
 
2923