/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: Canonical.com Patch Queue Manager
  • Date: 2010-02-11 04:02:41 UTC
  • mfrom: (5017.2.2 tariff)
  • Revision ID: pqm@pqm.ubuntu.com-20100211040241-w6n021dz0uus341n
(mbp) add import-tariff tests

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