/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-11 08:44:59 UTC
  • mfrom: (5221 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100511084459-pb0uinna9zs3wu59
Merge trunk - resolve conflicts.

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