/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 breezy/bzr/pack_repo.py

  • Committer: Jelmer Vernooij
  • Date: 2020-05-06 02:13:25 UTC
  • mfrom: (7490.7.21 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200506021325-awbmmqu1zyorz7sj
Merge 3.1 branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
20
from ..lazy_import import lazy_import
23
21
lazy_import(globals(), """
24
 
from itertools import izip
 
22
import contextlib
25
23
import time
26
24
 
27
25
from breezy import (
28
 
    cleanup,
29
26
    config,
30
27
    debug,
31
28
    graph,
32
29
    osutils,
33
30
    transactions,
34
 
    tsort,
35
31
    ui,
36
32
    )
37
33
from breezy.bzr import (
38
 
    chk_map,
39
34
    pack,
40
35
    )
41
36
from breezy.bzr.index import (
42
37
    CombinedGraphIndex,
43
 
    GraphIndexPrefixAdapter,
44
38
    )
45
39
""")
46
40
from .. import (
64
58
    MetaDirRepository,
65
59
    RepositoryFormatMetaDir,
66
60
    )
67
 
from ..sixish import (
68
 
    reraise,
69
 
    )
70
61
from ..bzr.vf_repository import (
71
62
    MetaDirVersionedFileRepository,
72
63
    MetaDirVersionedFileRepositoryFormat,
73
64
    VersionedFileCommitBuilder,
74
 
    VersionedFileRootCommitBuilder,
75
65
    )
76
66
from ..trace import (
77
67
    mutter,
91
81
                 timezone=None, committer=None, revprops=None,
92
82
                 revision_id=None, lossy=False):
93
83
        VersionedFileCommitBuilder.__init__(self, repository, parents, config,
94
 
            timestamp=timestamp, timezone=timezone, committer=committer,
95
 
            revprops=revprops, revision_id=revision_id, lossy=lossy)
96
 
        self._file_graph = graph.Graph(
97
 
            repository._pack_collection.text_index.combined_index)
98
 
 
99
 
    def _heads(self, file_id, revision_ids):
100
 
        keys = [(file_id, revision_id) for revision_id in revision_ids]
101
 
        return {key[1] for key in self._file_graph.heads(keys)}
102
 
 
103
 
 
104
 
class PackRootCommitBuilder(VersionedFileRootCommitBuilder):
105
 
    """A subclass of RootCommitBuilder to add texts with pack semantics.
106
 
 
107
 
    Specifically this uses one knit object rather than one knit object per
108
 
    added text, reducing memory and object pressure.
109
 
    """
110
 
 
111
 
    def __init__(self, repository, parents, config, timestamp=None,
112
 
                 timezone=None, committer=None, revprops=None,
113
 
                 revision_id=None, lossy=False):
114
 
        super(PackRootCommitBuilder, self).__init__(repository, parents,
115
 
            config, timestamp=timestamp, timezone=timezone,
116
 
            committer=committer, revprops=revprops, revision_id=revision_id,
117
 
            lossy=lossy)
 
84
                                            timestamp=timestamp, timezone=timezone, committer=committer,
 
85
                                            revprops=revprops, revision_id=revision_id, lossy=lossy)
118
86
        self._file_graph = graph.Graph(
119
87
            repository._pack_collection.text_index.combined_index)
120
88
 
141
109
        }
142
110
 
143
111
    def __init__(self, revision_index, inventory_index, text_index,
144
 
        signature_index, chk_index=None):
 
112
                 signature_index, chk_index=None):
145
113
        """Create a pack instance.
146
114
 
147
115
        :param revision_index: A GraphIndex for determining what revisions are
177
145
        """
178
146
        missing_items = {}
179
147
        for (index_name, external_refs, index) in [
180
 
            ('texts',
181
 
                self._get_external_refs(self.text_index),
182
 
                self._pack_collection.text_index.combined_index),
183
 
            ('inventories',
184
 
                self._get_external_refs(self.inventory_index),
185
 
                self._pack_collection.inventory_index.combined_index),
186
 
            ]:
 
148
                ('texts',
 
149
                    self._get_external_refs(self.text_index),
 
150
                    self._pack_collection.text_index.combined_index),
 
151
                ('inventories',
 
152
                    self._get_external_refs(self.inventory_index),
 
153
                    self._pack_collection.inventory_index.combined_index),
 
154
                ]:
187
155
            missing = external_refs.difference(
188
156
                k for (idx, k, v, r) in
189
157
                index.iter_entries(external_refs))
232
200
        if index_type == 'chk':
233
201
            unlimited_cache = True
234
202
        index = self.index_class(self.index_transport,
235
 
                    self.index_name(index_type, self.name),
236
 
                    self.index_sizes[self.index_offset(index_type)],
237
 
                    unlimited_cache=unlimited_cache)
 
203
                                 self.index_name(index_type, self.name),
 
204
                                 self.index_sizes[self.index_offset(
 
205
                                     index_type)],
 
206
                                 unlimited_cache=unlimited_cache)
238
207
        if index_type == 'chk':
239
208
            index._leaf_factory = btree_index._gcchk_factory
240
209
        setattr(self, index_type + '_index', index)
241
210
 
 
211
    def __lt__(self, other):
 
212
        if not isinstance(other, Pack):
 
213
            raise TypeError(other)
 
214
        return (id(self) < id(other))
 
215
 
 
216
    def __hash__(self):
 
217
        return hash((type(self), self.revision_index, self.inventory_index,
 
218
                     self.text_index, self.signature_index, self.chk_index))
 
219
 
242
220
 
243
221
class ExistingPack(Pack):
244
222
    """An in memory proxy for an existing .pack and its disk indices."""
245
223
 
246
224
    def __init__(self, pack_transport, name, revision_index, inventory_index,
247
 
        text_index, signature_index, chk_index=None):
 
225
                 text_index, signature_index, chk_index=None):
248
226
        """Create an ExistingPack object.
249
227
 
250
228
        :param pack_transport: The transport where the pack file resides.
251
229
        :param name: The name of the pack on disk in the pack_transport.
252
230
        """
253
231
        Pack.__init__(self, revision_index, inventory_index, text_index,
254
 
            signature_index, chk_index)
 
232
                      signature_index, chk_index)
255
233
        self.name = name
256
234
        self.pack_transport = pack_transport
257
235
        if None in (revision_index, inventory_index, text_index,
258
 
                signature_index, name, pack_transport):
 
236
                    signature_index, name, pack_transport):
259
237
            raise AssertionError()
260
238
 
261
239
    def __eq__(self, other):
269
247
            self.__class__.__module__, self.__class__.__name__, id(self),
270
248
            self.pack_transport, self.name)
271
249
 
 
250
    def __hash__(self):
 
251
        return hash((type(self), self.name))
 
252
 
272
253
 
273
254
class ResumedPack(ExistingPack):
274
255
 
275
256
    def __init__(self, name, revision_index, inventory_index, text_index,
276
 
        signature_index, upload_transport, pack_transport, index_transport,
277
 
        pack_collection, chk_index=None):
 
257
                 signature_index, upload_transport, pack_transport, index_transport,
 
258
                 pack_collection, chk_index=None):
278
259
        """Create a ResumedPack object."""
279
260
        ExistingPack.__init__(self, pack_transport, name, revision_index,
280
 
            inventory_index, text_index, signature_index,
281
 
            chk_index=chk_index)
 
261
                              inventory_index, text_index, signature_index,
 
262
                              chk_index=chk_index)
282
263
        self.upload_transport = upload_transport
283
264
        self.index_transport = index_transport
284
265
        self.index_sizes = [None, None, None, None]
310
291
    def abort(self):
311
292
        self.upload_transport.delete(self.file_name())
312
293
        indices = [self.revision_index, self.inventory_index, self.text_index,
313
 
            self.signature_index]
 
294
                   self.signature_index]
314
295
        if self.chk_index is not None:
315
296
            indices.append(self.chk_index)
316
297
        for index in indices:
358
339
        else:
359
340
            chk_index = None
360
341
        Pack.__init__(self,
361
 
            # Revisions: parents list, no text compression.
362
 
            index_builder_class(reference_lists=1),
363
 
            # Inventory: We want to map compression only, but currently the
364
 
            # knit code hasn't been updated enough to understand that, so we
365
 
            # have a regular 2-list index giving parents and compression
366
 
            # source.
367
 
            index_builder_class(reference_lists=2),
368
 
            # Texts: compression and per file graph, for all fileids - so two
369
 
            # reference lists and two elements in the key tuple.
370
 
            index_builder_class(reference_lists=2, key_elements=2),
371
 
            # Signatures: Just blobs to store, no compression, no parents
372
 
            # listing.
373
 
            index_builder_class(reference_lists=0),
374
 
            # CHK based storage - just blobs, no compression or parents.
375
 
            chk_index=chk_index
376
 
            )
 
342
                      # Revisions: parents list, no text compression.
 
343
                      index_builder_class(reference_lists=1),
 
344
                      # Inventory: We want to map compression only, but currently the
 
345
                      # knit code hasn't been updated enough to understand that, so we
 
346
                      # have a regular 2-list index giving parents and compression
 
347
                      # source.
 
348
                      index_builder_class(reference_lists=2),
 
349
                      # Texts: compression and per file graph, for all fileids - so two
 
350
                      # reference lists and two elements in the key tuple.
 
351
                      index_builder_class(reference_lists=2, key_elements=2),
 
352
                      # Signatures: Just blobs to store, no compression, no parents
 
353
                      # listing.
 
354
                      index_builder_class(reference_lists=0),
 
355
                      # CHK based storage - just blobs, no compression or parents.
 
356
                      chk_index=chk_index
 
357
                      )
377
358
        self._pack_collection = pack_collection
378
359
        # When we make readonly indices, we need this.
379
360
        self.index_class = pack_collection._index_class
404
385
            self.random_name, mode=self._file_mode)
405
386
        if 'pack' in debug.debug_flags:
406
387
            mutter('%s: create_pack: pack stream open: %s%s t+%6.3fs',
407
 
                time.ctime(), self.upload_transport.base, self.random_name,
408
 
                time.time() - self.start_time)
 
388
                   time.ctime(), self.upload_transport.base, self.random_name,
 
389
                   time.time() - self.start_time)
409
390
        # A list of byte sequences to be written to the new pack, and the
410
391
        # aggregate size of them.  Stored as a list rather than separate
411
392
        # variables so that the _write_data closure below can update them.
415
396
        # robertc says- this is a closure rather than a method on the object
416
397
        # so that the variables are locals, and faster than accessing object
417
398
        # members.
 
399
 
418
400
        def _write_data(bytes, flush=False, _buffer=self._buffer,
419
 
            _write=self.write_stream.write, _update=self._hash.update):
 
401
                        _write=self.write_stream.write, _update=self._hash.update):
420
402
            _buffer[0].append(bytes)
421
403
            _buffer[1] += len(bytes)
422
404
            # buffer cap
455
437
    def data_inserted(self):
456
438
        """True if data has been added to this pack."""
457
439
        return bool(self.get_revision_count() or
458
 
            self.inventory_index.key_count() or
459
 
            self.text_index.key_count() or
460
 
            self.signature_index.key_count() or
461
 
            (self.chk_index is not None and self.chk_index.key_count()))
 
440
                    self.inventory_index.key_count() or
 
441
                    self.text_index.key_count() or
 
442
                    self.signature_index.key_count() or
 
443
                    (self.chk_index is not None and self.chk_index.key_count()))
462
444
 
463
445
    def finish_content(self):
464
446
        if self.name is not None:
465
447
            return
466
448
        self._writer.end()
467
449
        if self._buffer[1]:
468
 
            self._write_data('', flush=True)
 
450
            self._write_data(b'', flush=True)
469
451
        self.name = self._hash.hexdigest()
470
452
 
471
453
    def finish(self, suspend=False):
489
471
        # they're in the names list.
490
472
        self.index_sizes = [None, None, None, None]
491
473
        self._write_index('revision', self.revision_index, 'revision',
492
 
            suspend)
 
474
                          suspend)
493
475
        self._write_index('inventory', self.inventory_index, 'inventory',
494
 
            suspend)
 
476
                          suspend)
495
477
        self._write_index('text', self.text_index, 'file texts', suspend)
496
478
        self._write_index('signature', self.signature_index,
497
 
            'revision signatures', suspend)
 
479
                          'revision signatures', suspend)
498
480
        if self.chk_index is not None:
499
481
            self.index_sizes.append(None)
500
482
            self._write_index('chk', self.chk_index,
501
 
                'content hash bytes', suspend)
 
483
                              'content hash bytes', suspend)
502
484
        self.write_stream.close(
503
485
            want_fdatasync=self._pack_collection.config_stack.get('repository.fdatasync'))
504
486
        # Note that this will clobber an existing pack with the same name,
520
502
        if 'pack' in debug.debug_flags:
521
503
            # XXX: size might be interesting?
522
504
            mutter('%s: create_pack: pack finished: %s%s->%s t+%6.3fs',
523
 
                time.ctime(), self.upload_transport.base, self.random_name,
524
 
                new_name, time.time() - self.start_time)
 
505
                   time.ctime(), self.upload_transport.base, self.random_name,
 
506
                   new_name, time.time() - self.start_time)
525
507
 
526
508
    def flush(self):
527
509
        """Flush any current data."""
552
534
        index_tempfile = index.finish()
553
535
        index_bytes = index_tempfile.read()
554
536
        write_stream = transport.open_write_stream(index_name,
555
 
            mode=self._file_mode)
 
537
                                                   mode=self._file_mode)
556
538
        write_stream.write(index_bytes)
557
539
        write_stream.close(
558
540
            want_fdatasync=self._pack_collection.config_stack.get('repository.fdatasync'))
560
542
        if 'pack' in debug.debug_flags:
561
543
            # XXX: size might be interesting?
562
544
            mutter('%s: create_pack: wrote %s index: %s%s t+%6.3fs',
563
 
                time.ctime(), label, self.upload_transport.base,
564
 
                self.random_name, time.time() - self.start_time)
 
545
                   time.ctime(), label, self.upload_transport.base,
 
546
                   self.random_name, time.time() - self.start_time)
565
547
        # Replace the writable index on this object with a readonly,
566
548
        # presently unloaded index. We should alter
567
549
        # the index layer to make its finish() error if add_node is
624
606
        """
625
607
        if self.add_callback is not None:
626
608
            raise AssertionError(
627
 
                "%s already has a writable index through %s" % \
 
609
                "%s already has a writable index through %s" %
628
610
                (self, self.add_callback))
629
611
        # allow writing: queue writes to a new index
630
612
        self.add_index(index, pack)
650
632
        del self.combined_index._indices[pos]
651
633
        del self.combined_index._index_names[pos]
652
634
        if (self.add_callback is not None and
653
 
            getattr(index, 'add_nodes', None) == self.add_callback):
 
635
                getattr(index, 'add_nodes', None) == self.add_callback):
654
636
            self.add_callback = None
655
637
            self.data_access.set_writer(None, None, (None, None))
656
638
 
714
696
            else:
715
697
                self.revision_ids = frozenset(self.revision_ids)
716
698
                self.revision_keys = frozenset((revid,) for revid in
717
 
                    self.revision_ids)
 
699
                                               self.revision_ids)
718
700
        if pb is None:
719
701
            self.pb = ui.ui_factory.nested_progress_bar()
720
702
        else:
728
710
    def open_pack(self):
729
711
        """Open a pack for the pack we are creating."""
730
712
        new_pack = self._pack_collection.pack_factory(self._pack_collection,
731
 
                upload_suffix=self.suffix,
732
 
                file_mode=self._pack_collection.repo.controldir._get_file_mode())
 
713
                                                      upload_suffix=self.suffix,
 
714
                                                      file_mode=self._pack_collection.repo.controldir._get_file_mode())
733
715
        # We know that we will process all nodes in order, and don't need to
734
716
        # query, so don't combine any indices spilled to disk until we are done
735
717
        new_pack.revision_index.set_optimize(combine_backing_indices=False)
760
742
    def _log_copied_texts(self):
761
743
        if 'pack' in debug.debug_flags:
762
744
            mutter('%s: create_pack: file texts copied: %s%s %d items t+%6.3fs',
763
 
                time.ctime(), self._pack_collection._upload_transport.base,
764
 
                self.new_pack.random_name,
765
 
                self.new_pack.text_index.key_count(),
766
 
                time.time() - self.new_pack.start_time)
 
745
                   time.ctime(), self._pack_collection._upload_transport.base,
 
746
                   self.new_pack.random_name,
 
747
                   self.new_pack.text_index.key_count(),
 
748
                   time.time() - self.new_pack.start_time)
767
749
 
768
750
    def _use_pack(self, new_pack):
769
751
        """Return True if new_pack should be used.
809
791
        self._index_builder_class = index_builder_class
810
792
        self._index_class = index_class
811
793
        self._suffix_offsets = {'.rix': 0, '.iix': 1, '.tix': 2, '.six': 3,
812
 
            '.cix': 4}
 
794
                                '.cix': 4}
813
795
        self.packs = []
814
796
        # name:Pack mapping
815
797
        self._names = None
825
807
        self.text_index = AggregateIndex(self.reload_pack_names, flush)
826
808
        self.signature_index = AggregateIndex(self.reload_pack_names, flush)
827
809
        all_indices = [self.revision_index, self.inventory_index,
828
 
                self.text_index, self.signature_index]
 
810
                       self.text_index, self.signature_index]
829
811
        if use_chk_index:
830
812
            self.chk_index = AggregateIndex(self.reload_pack_names, flush)
831
813
            all_indices.append(self.chk_index)
928
910
        num_old_packs = sum([len(po[1]) for po in pack_operations])
929
911
        num_revs_affected = sum([po[0] for po in pack_operations])
930
912
        mutter('Auto-packing repository %s, which has %d pack files, '
931
 
            'containing %d revisions. Packing %d files into %d affecting %d'
932
 
            ' revisions', self, total_packs, total_revisions, num_old_packs,
933
 
            num_new_packs, num_revs_affected)
 
913
               'containing %d revisions. Packing %d files into %d affecting %d'
 
914
               ' revisions', str(
 
915
                   self), total_packs, total_revisions, num_old_packs,
 
916
               num_new_packs, num_revs_affected)
934
917
        result = self._execute_pack_operations(pack_operations, packer_class=self.normal_packer_class,
935
 
                                      reload_func=self._restart_autopack)
936
 
        mutter('Auto-packing repository %s completed', self)
 
918
                                               reload_func=self._restart_autopack)
 
919
        mutter('Auto-packing repository %s completed', str(self))
937
920
        return result
938
921
 
939
922
    def _execute_pack_operations(self, pack_operations, packer_class,
940
 
            reload_func=None):
 
923
                                 reload_func=None):
941
924
        """Execute a series of pack operations.
942
925
 
943
926
        :param pack_operations: A list of [revision_count, packs_to_combine].
949
932
            if len(packs) == 0:
950
933
                continue
951
934
            packer = packer_class(self, packs, '.autopack',
952
 
                                   reload_func=reload_func)
 
935
                                  reload_func=reload_func)
953
936
            try:
954
937
                result = packer.pack()
955
938
            except errors.RetryWithNewPacks:
999
982
        # XXX: the following may want to be a class, to pack with a given
1000
983
        # policy.
1001
984
        mutter('Packing repository %s, which has %d pack files, '
1002
 
            'containing %d revisions with hint %r.', self, total_packs,
1003
 
            total_revisions, hint)
 
985
               'containing %d revisions with hint %r.', str(self), total_packs,
 
986
               total_revisions, hint)
1004
987
        while True:
1005
988
            try:
1006
989
                self._try_pack_operations(hint)
1024
1007
                pack_operations[-1][0] += pack.get_revision_count()
1025
1008
                pack_operations[-1][1].append(pack)
1026
1009
        self._execute_pack_operations(pack_operations,
1027
 
            packer_class=self.optimising_packer_class,
1028
 
            reload_func=self._restart_pack_operations)
 
1010
                                      packer_class=self.optimising_packer_class,
 
1011
                                      reload_func=self._restart_pack_operations)
1029
1012
 
1030
1013
    def plan_autopack_combinations(self, existing_packs, pack_distribution):
1031
1014
        """Plan a pack operation.
1075
1058
            final_pack_list.extend(pack_files)
1076
1059
        if len(final_pack_list) == 1:
1077
1060
            raise AssertionError('We somehow generated an autopack with a'
1078
 
                ' single pack file being moved.')
 
1061
                                 ' single pack file being moved.')
1079
1062
            return []
1080
1063
        return [[final_rev_count, final_pack_list]]
1081
1064
 
1092
1075
            self._names = {}
1093
1076
            self._packs_at_load = set()
1094
1077
            for index, key, value in self._iter_disk_pack_index():
1095
 
                name = key[0]
 
1078
                name = key[0].decode('ascii')
1096
1079
                self._names[name] = self._parse_index_sizes(value)
1097
 
                self._packs_at_load.add((key, value))
 
1080
                self._packs_at_load.add((name, value))
1098
1081
            result = True
1099
1082
        else:
1100
1083
            result = False
1104
1087
 
1105
1088
    def _parse_index_sizes(self, value):
1106
1089
        """Parse a string of index sizes."""
1107
 
        return tuple([int(digits) for digits in value.split(' ')])
 
1090
        return tuple(int(digits) for digits in value.split(b' '))
1108
1091
 
1109
1092
    def get_pack_by_name(self, name):
1110
1093
        """Get a Pack object by name.
1124
1107
            else:
1125
1108
                chk_index = None
1126
1109
            result = ExistingPack(self._pack_transport, name, rev_index,
1127
 
                inv_index, txt_index, sig_index, chk_index)
 
1110
                                  inv_index, txt_index, sig_index, chk_index)
1128
1111
            self.add_pack_to_memory(result)
1129
1112
            return result
1130
1113
 
1150
1133
            else:
1151
1134
                chk_index = None
1152
1135
            result = self.resumed_pack_factory(name, rev_index, inv_index,
1153
 
                txt_index, sig_index, self._upload_transport,
1154
 
                self._pack_transport, self._index_transport, self,
1155
 
                chk_index=chk_index)
 
1136
                                               txt_index, sig_index, self._upload_transport,
 
1137
                                               self._pack_transport, self._index_transport, self,
 
1138
                                               chk_index=chk_index)
1156
1139
        except errors.NoSuchFile as e:
1157
1140
            raise errors.UnresumableWriteGroup(self.repo, [name], str(e))
1158
1141
        self.add_pack_to_memory(result)
1180
1163
        :return: An iterator of the index contents.
1181
1164
        """
1182
1165
        return self._index_class(self.transport, 'pack-names', None
1183
 
                ).iter_all_entries()
 
1166
                                 ).iter_all_entries()
1184
1167
 
1185
1168
    def _make_index(self, name, suffix, resume=False, is_chk=False):
1186
1169
        size_offset = self._suffix_offsets[suffix]
1193
1176
            index_size = self._names[name][size_offset]
1194
1177
        index = self._index_class(transport, index_name, index_size,
1195
1178
                                  unlimited_cache=is_chk)
1196
 
        if is_chk and self._index_class is btree_index.BTreeGraphIndex: 
 
1179
        if is_chk and self._index_class is btree_index.BTreeGraphIndex:
1197
1180
            index._leaf_factory = btree_index._gcchk_factory
1198
1181
        return index
1199
1182
 
1232
1215
            try:
1233
1216
                try:
1234
1217
                    pack.pack_transport.move(pack.file_name(),
1235
 
                        '../obsolete_packs/' + pack.file_name())
 
1218
                                             '../obsolete_packs/' + pack.file_name())
1236
1219
                except errors.NoSuchFile:
1237
1220
                    # perhaps obsolete_packs was removed? Let's create it and
1238
1221
                    # try again
1241
1224
                    except errors.FileExists:
1242
1225
                        pass
1243
1226
                    pack.pack_transport.move(pack.file_name(),
1244
 
                        '../obsolete_packs/' + pack.file_name())
 
1227
                                             '../obsolete_packs/' + pack.file_name())
1245
1228
            except (errors.PathError, errors.TransportError) as e:
1246
1229
                # TODO: Should these be warnings or mutters?
1247
1230
                mutter("couldn't rename obsolete pack, skipping it:\n%s"
1255
1238
            for suffix in suffixes:
1256
1239
                try:
1257
1240
                    self._index_transport.move(pack.name + suffix,
1258
 
                        '../obsolete_packs/' + pack.name + suffix)
 
1241
                                               '../obsolete_packs/' + pack.name + suffix)
1259
1242
                except (errors.PathError, errors.TransportError) as e:
1260
1243
                    mutter("couldn't rename obsolete index, skipping it:\n%s"
1261
1244
                           % (e,))
1343
1326
        # load the disk nodes across
1344
1327
        disk_nodes = set()
1345
1328
        for index, key, value in self._iter_disk_pack_index():
1346
 
            disk_nodes.add((key, value))
 
1329
            disk_nodes.add((key[0].decode('ascii'), value))
1347
1330
        orig_disk_nodes = set(disk_nodes)
1348
1331
 
1349
1332
        # do a two-way diff against our original content
1350
1333
        current_nodes = set()
1351
1334
        for name, sizes in self._names.items():
1352
1335
            current_nodes.add(
1353
 
                ((name, ), ' '.join(str(size) for size in sizes)))
 
1336
                (name, b' '.join(b'%d' % size for size in sizes)))
1354
1337
 
1355
1338
        # Packs no longer present in the repository, which were present when we
1356
1339
        # locked the repository
1380
1363
        new_names = dict(disk_nodes)
1381
1364
        # drop no longer present nodes
1382
1365
        for pack in self.all_packs():
1383
 
            if (pack.name,) not in new_names:
 
1366
            if pack.name not in new_names:
1384
1367
                removed.append(pack.name)
1385
1368
                self._remove_pack_from_memory(pack)
1386
1369
        # add new nodes/refresh existing ones
1387
 
        for key, value in disk_nodes:
1388
 
            name = key[0]
 
1370
        for name, value in disk_nodes:
1389
1371
            sizes = self._parse_index_sizes(value)
1390
1372
            if name in self._names:
1391
1373
                # existing
1432
1414
            # TODO: handle same-name, index-size-changes here -
1433
1415
            # e.g. use the value from disk, not ours, *unless* we're the one
1434
1416
            # changing it.
1435
 
            for key, value in disk_nodes:
1436
 
                builder.add_node(key, value)
 
1417
            for name, value in disk_nodes:
 
1418
                builder.add_node((name.encode('ascii'), ), value)
1437
1419
            self.transport.put_file('pack-names', builder.finish(),
1438
 
                mode=self.repo.controldir._get_file_mode())
 
1420
                                    mode=self.repo.controldir._get_file_mode())
1439
1421
            self._packs_at_load = disk_nodes
1440
1422
            if clear_obsolete_packs:
1441
1423
                to_preserve = None
1455
1437
            obsolete_packs = [o for o in obsolete_packs
1456
1438
                              if o.name not in already_obsolete]
1457
1439
            self._obsolete_packs(obsolete_packs)
1458
 
        return [new_node[0][0] for new_node in new_nodes]
 
1440
        return [new_node[0] for new_node in new_nodes]
1459
1441
 
1460
1442
    def reload_pack_names(self):
1461
1443
        """Sync our pack listing with what is present in the repository.
1467
1449
        :return: True if the in-memory list of packs has been altered at all.
1468
1450
        """
1469
1451
        # The ensure_loaded call is to handle the case where the first call
1470
 
        # made involving the collection was to reload_pack_names, where we 
 
1452
        # made involving the collection was to reload_pack_names, where we
1471
1453
        # don't have a view of disk contents. It's a bit of a bandaid, and
1472
1454
        # causes two reads of pack-names, but it's a rare corner case not
1473
1455
        # struck with regular push/pull etc.
1535
1517
        if not self.repo.is_write_locked():
1536
1518
            raise errors.NotWriteLocked(self)
1537
1519
        self._new_pack = self.pack_factory(self, upload_suffix='.pack',
1538
 
            file_mode=self.repo.controldir._get_file_mode())
 
1520
                                           file_mode=self.repo.controldir._get_file_mode())
1539
1521
        # allow writing: queue writes to a new index
1540
1522
        self.revision_index.add_writable_index(self._new_pack.revision_index,
1541
 
            self._new_pack)
 
1523
                                               self._new_pack)
1542
1524
        self.inventory_index.add_writable_index(self._new_pack.inventory_index,
1543
 
            self._new_pack)
 
1525
                                                self._new_pack)
1544
1526
        self.text_index.add_writable_index(self._new_pack.text_index,
1545
 
            self._new_pack)
 
1527
                                           self._new_pack)
1546
1528
        self._new_pack.text_index.set_optimize(combine_backing_indices=False)
1547
1529
        self.signature_index.add_writable_index(self._new_pack.signature_index,
1548
 
            self._new_pack)
 
1530
                                                self._new_pack)
1549
1531
        if self.chk_index is not None:
1550
1532
            self.chk_index.add_writable_index(self._new_pack.chk_index,
1551
 
                self._new_pack)
 
1533
                                              self._new_pack)
1552
1534
            self.repo.chk_bytes._index._add_callback = self.chk_index.add_callback
1553
 
            self._new_pack.chk_index.set_optimize(combine_backing_indices=False)
 
1535
            self._new_pack.chk_index.set_optimize(
 
1536
                combine_backing_indices=False)
1554
1537
 
1555
1538
        self.repo.inventories._index._add_callback = self.inventory_index.add_callback
1556
1539
        self.repo.revisions._index._add_callback = self.revision_index.add_callback
1561
1544
        # FIXME: just drop the transient index.
1562
1545
        # forget what names there are
1563
1546
        if self._new_pack is not None:
1564
 
            operation = cleanup.OperationWithCleanups(self._new_pack.abort)
1565
 
            operation.add_cleanup(setattr, self, '_new_pack', None)
1566
 
            # If we aborted while in the middle of finishing the write
1567
 
            # group, _remove_pack_indices could fail because the indexes are
1568
 
            # already gone.  But they're not there we shouldn't fail in this
1569
 
            # case, so we pass ignore_missing=True.
1570
 
            operation.add_cleanup(self._remove_pack_indices, self._new_pack,
1571
 
                ignore_missing=True)
1572
 
            operation.run_simple()
 
1547
            with contextlib.ExitStack() as stack:
 
1548
                stack.callback(setattr, self, '_new_pack', None)
 
1549
                # If we aborted while in the middle of finishing the write
 
1550
                # group, _remove_pack_indices could fail because the indexes are
 
1551
                # already gone.  But they're not there we shouldn't fail in this
 
1552
                # case, so we pass ignore_missing=True.
 
1553
                stack.callback(self._remove_pack_indices, self._new_pack,
 
1554
                               ignore_missing=True)
 
1555
                self._new_pack.abort()
1573
1556
        for resumed_pack in self._resumed_packs:
1574
 
            operation = cleanup.OperationWithCleanups(resumed_pack.abort)
1575
 
            # See comment in previous finally block.
1576
 
            operation.add_cleanup(self._remove_pack_indices, resumed_pack,
1577
 
                ignore_missing=True)
1578
 
            operation.run_simple()
 
1557
            with contextlib.ExitStack() as stack:
 
1558
                # See comment in previous finally block.
 
1559
                stack.callback(self._remove_pack_indices, resumed_pack,
 
1560
                               ignore_missing=True)
 
1561
                resumed_pack.abort()
1579
1562
        del self._resumed_packs[:]
1580
1563
 
1581
1564
    def _remove_resumed_pack_indices(self):
1592
1575
        # The base implementation does no checks.  GCRepositoryPackCollection
1593
1576
        # overrides this.
1594
1577
        return []
1595
 
        
 
1578
 
1596
1579
    def _commit_write_group(self):
1597
1580
        all_missing = set()
1598
1581
        for prefix, versioned_file in (
1606
1589
        if all_missing:
1607
1590
            raise errors.BzrCheckError(
1608
1591
                "Repository %s has missing compression parent(s) %r "
1609
 
                 % (self.repo, sorted(all_missing)))
 
1592
                % (self.repo, sorted(all_missing)))
1610
1593
        problems = self._check_new_inventories()
1611
1594
        if problems:
1612
1595
            problems_summary = '\n'.join(problems)
1693
1676
    _serializer = None
1694
1677
 
1695
1678
    def __init__(self, _format, a_controldir, control_files, _commit_builder_class,
1696
 
        _serializer):
 
1679
                 _serializer):
1697
1680
        MetaDirRepository.__init__(self, _format, a_controldir, control_files)
1698
1681
        self._commit_builder_class = _commit_builder_class
1699
1682
        self._serializer = _serializer
1825
1808
        recompress deltas or do other such expensive operations.
1826
1809
        """
1827
1810
        with self.lock_write():
1828
 
            self._pack_collection.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
 
1811
            self._pack_collection.pack(
 
1812
                hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1829
1813
 
1830
1814
    def reconcile(self, other=None, thorough=False):
1831
1815
        """Reconcile this repository."""
1832
 
        from breezy.reconcile import PackReconciler
 
1816
        from .reconcile import PackReconciler
1833
1817
        with self.lock_write():
1834
1818
            reconciler = PackReconciler(self, thorough=thorough)
1835
 
            reconciler.reconcile()
1836
 
            return reconciler
 
1819
            return reconciler.reconcile()
1837
1820
 
1838
1821
    def _reconcile_pack(self, collection, packs, extension, revs, pb):
1839
1822
        raise NotImplementedError(self._reconcile_pack)
1914
1897
        dirs = ['indices', 'obsolete_packs', 'packs', 'upload']
1915
1898
        builder = self.index_builder_class()
1916
1899
        files = [('pack-names', builder.finish())]
1917
 
        # GZ 2017-06-09: Where should format strings get decoded...
1918
 
        utf8_files = [('format', self.get_format_string().encode('ascii'))]
 
1900
        utf8_files = [('format', self.get_format_string())]
1919
1901
 
1920
 
        self._upload_blank_content(a_controldir, dirs, files, utf8_files, shared)
 
1902
        self._upload_blank_content(
 
1903
            a_controldir, dirs, files, utf8_files, shared)
1921
1904
        repository = self.open(a_controldir=a_controldir, _found=True)
1922
1905
        self._run_post_repo_init_hooks(repository, a_controldir, shared)
1923
1906
        return repository
1936
1919
        else:
1937
1920
            repo_transport = a_controldir.get_repository_transport(None)
1938
1921
        control_files = lockable_files.LockableFiles(repo_transport,
1939
 
                                'lock', lockdir.LockDir)
 
1922
                                                     'lock', lockdir.LockDir)
1940
1923
        return self.repository_class(_format=self,
1941
 
                              a_controldir=a_controldir,
1942
 
                              control_files=control_files,
1943
 
                              _commit_builder_class=self._commit_builder_class,
1944
 
                              _serializer=self._serializer)
 
1924
                                     a_controldir=a_controldir,
 
1925
                                     control_files=control_files,
 
1926
                                     _commit_builder_class=self._commit_builder_class,
 
1927
                                     _serializer=self._serializer)
1945
1928
 
1946
1929
 
1947
1930
class RetryPackOperations(errors.RetryWithNewPacks):
1975
1958
        self._reload_func = reload_func
1976
1959
        self._flush_func = flush_func
1977
1960
 
 
1961
    def add_raw_record(self, key, size, raw_data):
 
1962
        """Add raw knit bytes to a storage area.
 
1963
 
 
1964
        The data is spooled to the container writer in one bytes-record per
 
1965
        raw data item.
 
1966
 
 
1967
        :param key: key of the data segment
 
1968
        :param size: length of the data segment
 
1969
        :param raw_data: A bytestring containing the data.
 
1970
        :return: An opaque index memo For _DirectPackAccess the memo is
 
1971
            (index, pos, length), where the index field is the write_index
 
1972
            object supplied to the PackAccess object.
 
1973
        """
 
1974
        p_offset, p_length = self._container_writer.add_bytes_record(
 
1975
            raw_data, size, [])
 
1976
        return (self._write_index, p_offset, p_length)
 
1977
 
1978
1978
    def add_raw_records(self, key_sizes, raw_data):
1979
1979
        """Add raw knit bytes to a storage area.
1980
1980
 
1989
1989
            length), where the index field is the write_index object supplied
1990
1990
            to the PackAccess object.
1991
1991
        """
 
1992
        raw_data = b''.join(raw_data)
1992
1993
        if not isinstance(raw_data, bytes):
1993
1994
            raise AssertionError(
1994
1995
                'data must be plain bytes was %s' % type(raw_data))
1995
1996
        result = []
1996
1997
        offset = 0
1997
1998
        for key, size in key_sizes:
1998
 
            p_offset, p_length = self._container_writer.add_bytes_record(
1999
 
                raw_data[offset:offset+size], [])
 
1999
            result.append(
 
2000
                self.add_raw_record(key, size, [raw_data[offset:offset + size]]))
2000
2001
            offset += size
2001
 
            result.append((self._write_index, p_offset, p_length))
2002
2002
        return result
2003
2003
 
2004
2004
    def flush(self):
2093
2093
                is_error = True
2094
2094
        if is_error:
2095
2095
            # GZ 2017-03-27: No real reason this needs the original traceback.
2096
 
            reraise(*retry_exc.exc_info)
 
2096
            raise retry_exc.exc_info[1]