/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/knit.py

  • Committer: Jelmer Vernooij
  • Date: 2018-11-16 10:50:21 UTC
  • mfrom: (7164 work)
  • mto: This revision was merged to the branch mainline in revision 7165.
  • Revision ID: jelmer@jelmer.uk-20181116105021-xl419v2rh4aus1au
Merge trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2011 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
51
51
 
52
52
"""
53
53
 
 
54
from __future__ import absolute_import
54
55
 
55
 
from cStringIO import StringIO
56
 
from itertools import izip
57
56
import operator
58
57
import os
59
 
import sys
60
58
 
61
 
from bzrlib.lazy_import import lazy_import
 
59
from ..lazy_import import lazy_import
62
60
lazy_import(globals(), """
63
 
from bzrlib import (
64
 
    annotate,
 
61
import gzip
 
62
 
 
63
from breezy import (
65
64
    debug,
66
65
    diff,
67
 
    graph as _mod_graph,
68
 
    index as _mod_index,
69
 
    lru_cache,
70
 
    pack,
71
 
    progress,
 
66
    patiencediff,
72
67
    static_tuple,
73
68
    trace,
74
69
    tsort,
75
70
    tuned_gzip,
76
71
    ui,
77
72
    )
 
73
from breezy.bzr import (
 
74
    index as _mod_index,
 
75
    pack,
 
76
    )
 
77
 
 
78
from breezy.bzr import pack_repo
 
79
from breezy.i18n import gettext
78
80
""")
79
 
from bzrlib import (
 
81
from .. import (
 
82
    annotate,
80
83
    errors,
81
84
    osutils,
82
 
    patiencediff,
83
85
    )
84
 
from bzrlib.errors import (
85
 
    FileExists,
 
86
from ..errors import (
 
87
    InternalBzrError,
 
88
    InvalidRevisionId,
86
89
    NoSuchFile,
87
 
    KnitError,
88
 
    InvalidRevisionId,
89
 
    KnitCorrupt,
90
 
    KnitHeaderError,
91
90
    RevisionNotPresent,
92
 
    RevisionAlreadyPresent,
93
 
    SHA1KnitCorrupt,
94
91
    )
95
 
from bzrlib.osutils import (
 
92
from ..osutils import (
96
93
    contains_whitespace,
97
 
    contains_linebreaks,
98
94
    sha_string,
99
95
    sha_strings,
100
96
    split_lines,
101
97
    )
102
 
from bzrlib.versionedfile import (
 
98
from ..sixish import (
 
99
    BytesIO,
 
100
    range,
 
101
    viewitems,
 
102
    viewvalues,
 
103
    )
 
104
from ..bzr.versionedfile import (
 
105
    _KeyRefs,
103
106
    AbsentContentFactory,
104
107
    adapter_registry,
105
108
    ConstantMapper,
106
109
    ContentFactory,
107
 
    ChunkedContentFactory,
108
110
    sort_groupcompress,
109
 
    VersionedFile,
110
 
    VersionedFiles,
 
111
    VersionedFilesWithFallbacks,
111
112
    )
112
113
 
113
114
 
129
130
_STREAM_MIN_BUFFER_SIZE = 5*1024*1024
130
131
 
131
132
 
 
133
class KnitError(InternalBzrError):
 
134
 
 
135
    _fmt = "Knit error"
 
136
 
 
137
 
 
138
class KnitCorrupt(KnitError):
 
139
 
 
140
    _fmt = "Knit %(filename)s corrupt: %(how)s"
 
141
 
 
142
    def __init__(self, filename, how):
 
143
        KnitError.__init__(self)
 
144
        self.filename = filename
 
145
        self.how = how
 
146
 
 
147
 
 
148
class SHA1KnitCorrupt(KnitCorrupt):
 
149
 
 
150
    _fmt = ("Knit %(filename)s corrupt: sha-1 of reconstructed text does not "
 
151
        "match expected sha-1. key %(key)s expected sha %(expected)s actual "
 
152
        "sha %(actual)s")
 
153
 
 
154
    def __init__(self, filename, actual, expected, key, content):
 
155
        KnitError.__init__(self)
 
156
        self.filename = filename
 
157
        self.actual = actual
 
158
        self.expected = expected
 
159
        self.key = key
 
160
        self.content = content
 
161
 
 
162
 
 
163
class KnitDataStreamIncompatible(KnitError):
 
164
    # Not raised anymore, as we can convert data streams.  In future we may
 
165
    # need it again for more exotic cases, so we're keeping it around for now.
 
166
 
 
167
    _fmt = "Cannot insert knit data stream of format \"%(stream_format)s\" into knit of format \"%(target_format)s\"."
 
168
 
 
169
    def __init__(self, stream_format, target_format):
 
170
        self.stream_format = stream_format
 
171
        self.target_format = target_format
 
172
 
 
173
 
 
174
class KnitDataStreamUnknown(KnitError):
 
175
    # Indicates a data stream we don't know how to handle.
 
176
 
 
177
    _fmt = "Cannot parse knit data stream of format \"%(stream_format)s\"."
 
178
 
 
179
    def __init__(self, stream_format):
 
180
        self.stream_format = stream_format
 
181
 
 
182
 
 
183
class KnitHeaderError(KnitError):
 
184
 
 
185
    _fmt = 'Knit header error: %(badline)r unexpected for file "%(filename)s".'
 
186
 
 
187
    def __init__(self, badline, filename):
 
188
        KnitError.__init__(self)
 
189
        self.badline = badline
 
190
        self.filename = filename
 
191
 
 
192
 
 
193
class KnitIndexUnknownMethod(KnitError):
 
194
    """Raised when we don't understand the storage method.
 
195
 
 
196
    Currently only 'fulltext' and 'line-delta' are supported.
 
197
    """
 
198
 
 
199
    _fmt = ("Knit index %(filename)s does not have a known method"
 
200
            " in options: %(options)r")
 
201
 
 
202
    def __init__(self, filename, options):
 
203
        KnitError.__init__(self)
 
204
        self.filename = filename
 
205
        self.options = options
 
206
 
 
207
 
132
208
class KnitAdapter(object):
133
209
    """Base class for knit record adaption."""
134
210
 
179
255
            self._data._parse_record_unchecked(annotated_compressed_bytes)
180
256
        content, delta = self._annotate_factory.parse_record(factory.key[-1],
181
257
            contents, factory._build_details, None)
182
 
        return ''.join(content.text())
 
258
        return b''.join(content.text())
183
259
 
184
260
 
185
261
class DeltaAnnotatedToFullText(KnitAdapter):
192
268
        delta = self._annotate_factory.parse_line_delta(contents, rec[1],
193
269
            plain=True)
194
270
        compression_parent = factory.parents[0]
195
 
        basis_entry = self._basis_vf.get_record_stream(
196
 
            [compression_parent], 'unordered', True).next()
 
271
        basis_entry = next(self._basis_vf.get_record_stream(
 
272
            [compression_parent], 'unordered', True))
197
273
        if basis_entry.storage_kind == 'absent':
198
274
            raise errors.RevisionNotPresent(compression_parent, self._basis_vf)
199
275
        basis_chunks = basis_entry.get_bytes_as('chunked')
203
279
        basis_content = PlainKnitContent(basis_lines, compression_parent)
204
280
        basis_content.apply_delta(delta, rec[1])
205
281
        basis_content._should_strip_eol = factory._build_details[1]
206
 
        return ''.join(basis_content.text())
 
282
        return b''.join(basis_content.text())
207
283
 
208
284
 
209
285
class FTPlainToFullText(KnitAdapter):
215
291
            self._data._parse_record_unchecked(compressed_bytes)
216
292
        content, delta = self._plain_factory.parse_record(factory.key[-1],
217
293
            contents, factory._build_details, None)
218
 
        return ''.join(content.text())
 
294
        return b''.join(content.text())
219
295
 
220
296
 
221
297
class DeltaPlainToFullText(KnitAdapter):
228
304
        delta = self._plain_factory.parse_line_delta(contents, rec[1])
229
305
        compression_parent = factory.parents[0]
230
306
        # XXX: string splitting overhead.
231
 
        basis_entry = self._basis_vf.get_record_stream(
232
 
            [compression_parent], 'unordered', True).next()
 
307
        basis_entry = next(self._basis_vf.get_record_stream(
 
308
            [compression_parent], 'unordered', True))
233
309
        if basis_entry.storage_kind == 'absent':
234
310
            raise errors.RevisionNotPresent(compression_parent, self._basis_vf)
235
311
        basis_chunks = basis_entry.get_bytes_as('chunked')
239
315
        # one plain.
240
316
        content, _ = self._plain_factory.parse_record(rec[1], contents,
241
317
            factory._build_details, basis_content)
242
 
        return ''.join(content.text())
 
318
        return b''.join(content.text())
243
319
 
244
320
 
245
321
class KnitContentFactory(ContentFactory):
283
359
    def _create_network_bytes(self):
284
360
        """Create a fully serialised network version for transmission."""
285
361
        # storage_kind, key, parents, Noeol, raw_record
286
 
        key_bytes = '\x00'.join(self.key)
 
362
        key_bytes = b'\x00'.join(self.key)
287
363
        if self.parents is None:
288
 
            parent_bytes = 'None:'
 
364
            parent_bytes = b'None:'
289
365
        else:
290
 
            parent_bytes = '\t'.join('\x00'.join(key) for key in self.parents)
 
366
            parent_bytes = b'\t'.join(b'\x00'.join(key) for key in self.parents)
291
367
        if self._build_details[1]:
292
 
            noeol = 'N'
 
368
            noeol = b'N'
293
369
        else:
294
 
            noeol = ' '
295
 
        network_bytes = "%s\n%s\n%s\n%s%s" % (self.storage_kind, key_bytes,
 
370
            noeol = b' '
 
371
        network_bytes = b"%s\n%s\n%s\n%s%s" % (
 
372
            self.storage_kind.encode('ascii'), key_bytes,
296
373
            parent_bytes, noeol, self._raw_record)
297
374
        self._network_bytes = network_bytes
298
375
 
355
432
            else:
356
433
                # all the keys etc are contained in the bytes returned in the
357
434
                # first record.
358
 
                return ''
 
435
                return b''
359
436
        if storage_kind in ('chunked', 'fulltext'):
360
437
            chunks = self._generator._get_one_work(self.key).text()
361
438
            if storage_kind == 'chunked':
362
439
                return chunks
363
440
            else:
364
 
                return ''.join(chunks)
 
441
                return b''.join(chunks)
365
442
        raise errors.UnavailableRepresentation(self.key, storage_kind,
366
443
            self.storage_kind)
367
444
 
384
461
    :param bytes: The bytes of the record on the network.
385
462
    """
386
463
    start = line_end
387
 
    line_end = bytes.find('\n', start)
388
 
    key = tuple(bytes[start:line_end].split('\x00'))
 
464
    line_end = bytes.find(b'\n', start)
 
465
    key = tuple(bytes[start:line_end].split(b'\x00'))
389
466
    start = line_end + 1
390
 
    line_end = bytes.find('\n', start)
 
467
    line_end = bytes.find(b'\n', start)
391
468
    parent_line = bytes[start:line_end]
392
 
    if parent_line == 'None:':
 
469
    if parent_line == b'None:':
393
470
        parents = None
394
471
    else:
395
472
        parents = tuple(
396
 
            [tuple(segment.split('\x00')) for segment in parent_line.split('\t')
 
473
            [tuple(segment.split(b'\x00')) for segment in parent_line.split(b'\t')
397
474
             if segment])
398
475
    start = line_end + 1
399
 
    noeol = bytes[start] == 'N'
 
476
    noeol = bytes[start:start+1] == b'N'
400
477
    if 'ft' in storage_kind:
401
478
        method = 'fulltext'
402
479
    else:
412
489
class KnitContent(object):
413
490
    """Content of a knit version to which deltas can be applied.
414
491
 
415
 
    This is always stored in memory as a list of lines with \n at the end,
 
492
    This is always stored in memory as a list of lines with \\n at the end,
416
493
    plus a flag saying if the final ending is really there or not, because that
417
494
    corresponds to the on-disk knit representation.
418
495
    """
470
547
 
471
548
    def __init__(self, lines):
472
549
        KnitContent.__init__(self)
473
 
        self._lines = lines
 
550
        self._lines = list(lines)
474
551
 
475
552
    def annotate(self):
476
553
        """Return a list of (origin, text) for each content line."""
477
554
        lines = self._lines[:]
478
555
        if self._should_strip_eol:
479
556
            origin, last_line = lines[-1]
480
 
            lines[-1] = (origin, last_line.rstrip('\n'))
 
557
            lines[-1] = (origin, last_line.rstrip(b'\n'))
481
558
        return lines
482
559
 
483
560
    def apply_delta(self, delta, new_version_id):
491
568
    def text(self):
492
569
        try:
493
570
            lines = [text for origin, text in self._lines]
494
 
        except ValueError, e:
 
571
        except ValueError as e:
495
572
            # most commonly (only?) caused by the internal form of the knit
496
573
            # missing annotation information because of a bug - see thread
497
574
            # around 20071015
499
576
                "line in annotated knit missing annotation information: %s"
500
577
                % (e,))
501
578
        if self._should_strip_eol:
502
 
            lines[-1] = lines[-1].rstrip('\n')
 
579
            lines[-1] = lines[-1].rstrip(b'\n')
503
580
        return lines
504
581
 
505
582
    def copy(self):
506
 
        return AnnotatedKnitContent(self._lines[:])
 
583
        return AnnotatedKnitContent(self._lines)
507
584
 
508
585
 
509
586
class PlainKnitContent(KnitContent):
539
616
        lines = self._lines
540
617
        if self._should_strip_eol:
541
618
            lines = lines[:]
542
 
            lines[-1] = lines[-1].rstrip('\n')
 
619
            lines[-1] = lines[-1].rstrip(b'\n')
543
620
        return lines
544
621
 
545
622
 
598
675
        #       but the code itself doesn't really depend on that.
599
676
        #       Figure out a way to not require the overhead of turning the
600
677
        #       list back into tuples.
601
 
        lines = [tuple(line.split(' ', 1)) for line in content]
 
678
        lines = (tuple(line.split(b' ', 1)) for line in content)
602
679
        return AnnotatedKnitContent(lines)
603
680
 
604
681
    def parse_line_delta_iter(self, lines):
620
697
        """
621
698
        result = []
622
699
        lines = iter(lines)
623
 
        next = lines.next
624
700
 
625
701
        cache = {}
626
702
        def cache_and_return(line):
627
 
            origin, text = line.split(' ', 1)
 
703
            origin, text = line.split(b' ', 1)
628
704
            return cache.setdefault(origin, origin), text
629
705
 
630
706
        # walk through the lines parsing.
632
708
        # loop to minimise any performance impact
633
709
        if plain:
634
710
            for header in lines:
635
 
                start, end, count = [int(n) for n in header.split(',')]
636
 
                contents = [next().split(' ', 1)[1] for i in xrange(count)]
 
711
                start, end, count = [int(n) for n in header.split(b',')]
 
712
                contents = [next(lines).split(b' ', 1)[1] for _ in range(count)]
637
713
                result.append((start, end, count, contents))
638
714
        else:
639
715
            for header in lines:
640
 
                start, end, count = [int(n) for n in header.split(',')]
641
 
                contents = [tuple(next().split(' ', 1)) for i in xrange(count)]
 
716
                start, end, count = [int(n) for n in header.split(b',')]
 
717
                contents = [tuple(next(lines).split(b' ', 1))
 
718
                    for _ in range(count)]
642
719
                result.append((start, end, count, contents))
643
720
        return result
644
721
 
645
722
    def get_fulltext_content(self, lines):
646
723
        """Extract just the content lines from a fulltext."""
647
 
        return (line.split(' ', 1)[1] for line in lines)
 
724
        return (line.split(b' ', 1)[1] for line in lines)
648
725
 
649
726
    def get_linedelta_content(self, lines):
650
727
        """Extract just the content from a line delta.
653
730
        Only the actual content lines.
654
731
        """
655
732
        lines = iter(lines)
656
 
        next = lines.next
657
733
        for header in lines:
658
 
            header = header.split(',')
 
734
            header = header.split(b',')
659
735
            count = int(header[2])
660
 
            for i in xrange(count):
661
 
                origin, text = next().split(' ', 1)
 
736
            for _ in range(count):
 
737
                origin, text = next(lines).split(b' ', 1)
662
738
                yield text
663
739
 
664
740
    def lower_fulltext(self, content):
666
742
 
667
743
        see parse_fulltext which this inverts.
668
744
        """
669
 
        return ['%s %s' % (o, t) for o, t in content._lines]
 
745
        return [b'%s %s' % (o, t) for o, t in content._lines]
670
746
 
671
747
    def lower_line_delta(self, delta):
672
748
        """convert a delta into a serializable form.
677
753
        #       the origin is a valid utf-8 line, eventually we could remove it
678
754
        out = []
679
755
        for start, end, c, lines in delta:
680
 
            out.append('%d,%d,%d\n' % (start, end, c))
681
 
            out.extend(origin + ' ' + text
 
756
            out.append(b'%d,%d,%d\n' % (start, end, c))
 
757
            out.extend(origin + b' ' + text
682
758
                       for origin, text in lines)
683
759
        return out
684
760
 
686
762
        content = knit._get_content(key)
687
763
        # adjust for the fact that serialised annotations are only key suffixes
688
764
        # for this factory.
689
 
        if type(key) is tuple:
 
765
        if isinstance(key, tuple):
690
766
            prefix = key[:-1]
691
767
            origins = content.annotate()
692
768
            result = []
721
797
        while cur < num_lines:
722
798
            header = lines[cur]
723
799
            cur += 1
724
 
            start, end, c = [int(n) for n in header.split(',')]
 
800
            start, end, c = [int(n) for n in header.split(b',')]
725
801
            yield start, end, c, lines[cur:cur+c]
726
802
            cur += c
727
803
 
739
815
        Only the actual content lines.
740
816
        """
741
817
        lines = iter(lines)
742
 
        next = lines.next
743
818
        for header in lines:
744
 
            header = header.split(',')
 
819
            header = header.split(b',')
745
820
            count = int(header[2])
746
 
            for i in xrange(count):
747
 
                yield next()
 
821
            for _ in range(count):
 
822
                yield next(lines)
748
823
 
749
824
    def lower_fulltext(self, content):
750
825
        return content.text()
752
827
    def lower_line_delta(self, delta):
753
828
        out = []
754
829
        for start, end, c, lines in delta:
755
 
            out.append('%d,%d,%d\n' % (start, end, c))
 
830
            out.append(b'%d,%d,%d\n' % (start, end, c))
756
831
            out.extend(lines)
757
832
        return out
758
833
 
805
880
        writer.begin()
806
881
        index = _KnitGraphIndex(graph_index, lambda:True, parents=parents,
807
882
            deltas=delta, add_callback=graph_index.add_nodes)
808
 
        access = _DirectPackAccess({})
 
883
        access = pack_repo._DirectPackAccess({})
809
884
        access.set_writer(writer, graph_index, (transport, 'newpack'))
810
885
        result = KnitVersionedFiles(index, access,
811
886
            max_delta_chain=max_delta_chain)
845
920
            if compression_parent not in all_build_index_memos:
846
921
                next_keys.add(compression_parent)
847
922
        build_keys = next_keys
848
 
    return sum([index_memo[2] for index_memo
849
 
                in all_build_index_memos.itervalues()])
850
 
 
851
 
 
852
 
class KnitVersionedFiles(VersionedFiles):
 
923
    return sum(index_memo[2]
 
924
               for index_memo in viewvalues(all_build_index_memos))
 
925
 
 
926
 
 
927
class KnitVersionedFiles(VersionedFilesWithFallbacks):
853
928
    """Storage for many versioned files using knit compression.
854
929
 
855
930
    Backend storage is managed by indices and data objects.
873
948
            stored during insertion.
874
949
        :param reload_func: An function that can be called if we think we need
875
950
            to reload the pack listing and try again. See
876
 
            'bzrlib.repofmt.pack_repo.AggregateIndex' for the signature.
 
951
            'breezy.bzr.pack_repo.AggregateIndex' for the signature.
877
952
        """
878
953
        self._index = index
879
954
        self._access = data_access
882
957
            self._factory = KnitAnnotateFactory()
883
958
        else:
884
959
            self._factory = KnitPlainFactory()
885
 
        self._fallback_vfs = []
 
960
        self._immediate_fallback_vfs = []
886
961
        self._reload_func = reload_func
887
962
 
888
963
    def __repr__(self):
891
966
            self._index,
892
967
            self._access)
893
968
 
 
969
    def without_fallbacks(self):
 
970
        """Return a clone of this object without any fallbacks configured."""
 
971
        return KnitVersionedFiles(self._index, self._access,
 
972
            self._max_delta_chain, self._factory.annotated,
 
973
            self._reload_func)
 
974
 
894
975
    def add_fallback_versioned_files(self, a_versioned_files):
895
976
        """Add a source of texts for texts not present in this knit.
896
977
 
897
978
        :param a_versioned_files: A VersionedFiles object.
898
979
        """
899
 
        self._fallback_vfs.append(a_versioned_files)
 
980
        self._immediate_fallback_vfs.append(a_versioned_files)
900
981
 
901
982
    def add_lines(self, key, parents, lines, parent_texts=None,
902
983
        left_matching_blocks=None, nostore_sha=None, random_id=False,
909
990
            # indexes can't directly store that, so we give them
910
991
            # an empty tuple instead.
911
992
            parents = ()
912
 
        line_bytes = ''.join(lines)
 
993
        line_bytes = b''.join(lines)
913
994
        return self._add(key, lines, parents,
914
995
            parent_texts, left_matching_blocks, nostore_sha, random_id,
915
996
            line_bytes=line_bytes)
916
997
 
917
 
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
918
 
        """See VersionedFiles._add_text()."""
919
 
        self._index._check_write_ok()
920
 
        self._check_add(key, None, random_id, check_content=False)
921
 
        if text.__class__ is not str:
922
 
            raise errors.BzrBadParameterUnicode("text")
923
 
        if parents is None:
924
 
            # The caller might pass None if there is no graph data, but kndx
925
 
            # indexes can't directly store that, so we give them
926
 
            # an empty tuple instead.
927
 
            parents = ()
928
 
        return self._add(key, None, parents,
929
 
            None, None, nostore_sha, random_id,
930
 
            line_bytes=text)
931
 
 
932
998
    def _add(self, key, lines, parents, parent_texts,
933
999
        left_matching_blocks, nostore_sha, random_id,
934
1000
        line_bytes):
978
1044
        # Note: line_bytes is not modified to add a newline, that is tracked
979
1045
        #       via the no_eol flag. 'lines' *is* modified, because that is the
980
1046
        #       general values needed by the Content code.
981
 
        if line_bytes and line_bytes[-1] != '\n':
982
 
            options.append('no-eol')
 
1047
        if line_bytes and not line_bytes.endswith(b'\n'):
 
1048
            options.append(b'no-eol')
983
1049
            no_eol = True
984
1050
            # Copy the existing list, or create a new one
985
1051
            if lines is None:
987
1053
            else:
988
1054
                lines = lines[:]
989
1055
            # Replace the last line with one that ends in a final newline
990
 
            lines[-1] = lines[-1] + '\n'
 
1056
            lines[-1] = lines[-1] + b'\n'
991
1057
        if lines is None:
992
1058
            lines = osutils.split_lines(line_bytes)
993
1059
 
994
1060
        for element in key[:-1]:
995
 
            if type(element) is not str:
996
 
                raise TypeError("key contains non-strings: %r" % (key,))
 
1061
            if not isinstance(element, bytes):
 
1062
                raise TypeError("key contains non-bytestrings: %r" % (key,))
997
1063
        if key[-1] is None:
998
 
            key = key[:-1] + ('sha1:' + digest,)
999
 
        elif type(key[-1]) is not str:
1000
 
                raise TypeError("key contains non-strings: %r" % (key,))
 
1064
            key = key[:-1] + (b'sha1:' + digest,)
 
1065
        elif not isinstance(key[-1], bytes):
 
1066
            raise TypeError("key contains non-bytestrings: %r" % (key,))
1001
1067
        # Knit hunks are still last-element only
1002
1068
        version_id = key[-1]
1003
1069
        content = self._factory.make(lines, version_id)
1012
1078
                left_matching_blocks)
1013
1079
 
1014
1080
        if delta:
1015
 
            options.append('line-delta')
 
1081
            options.append(b'line-delta')
1016
1082
            store_lines = self._factory.lower_line_delta(delta_hunks)
1017
 
            size, bytes = self._record_to_data(key, digest,
 
1083
            size, data = self._record_to_data(key, digest,
1018
1084
                store_lines)
1019
1085
        else:
1020
 
            options.append('fulltext')
 
1086
            options.append(b'fulltext')
1021
1087
            # isinstance is slower and we have no hierarchy.
1022
1088
            if self._factory.__class__ is KnitPlainFactory:
1023
1089
                # Use the already joined bytes saving iteration time in
1024
1090
                # _record_to_data.
1025
1091
                dense_lines = [line_bytes]
1026
1092
                if no_eol:
1027
 
                    dense_lines.append('\n')
1028
 
                size, bytes = self._record_to_data(key, digest,
 
1093
                    dense_lines.append(b'\n')
 
1094
                size, data = self._record_to_data(key, digest,
1029
1095
                    lines, dense_lines)
1030
1096
            else:
1031
1097
                # get mixed annotation + content and feed it into the
1032
1098
                # serialiser.
1033
1099
                store_lines = self._factory.lower_fulltext(content)
1034
 
                size, bytes = self._record_to_data(key, digest,
 
1100
                size, data = self._record_to_data(key, digest,
1035
1101
                    store_lines)
1036
1102
 
1037
 
        access_memo = self._access.add_raw_records([(key, size)], bytes)[0]
 
1103
        access_memo = self._access.add_raw_records([(key, size)], data)[0]
1038
1104
        self._index.add_records(
1039
1105
            ((key, options, access_memo, parents),),
1040
1106
            random_id=random_id)
1066
1132
            if self._index.get_method(key) != 'fulltext':
1067
1133
                compression_parent = parent_map[key][0]
1068
1134
                if compression_parent not in parent_map:
1069
 
                    raise errors.KnitCorrupt(self,
 
1135
                    raise KnitCorrupt(self,
1070
1136
                        "Missing basis parent %s for %s" % (
1071
1137
                        compression_parent, key))
1072
 
        for fallback_vfs in self._fallback_vfs:
 
1138
        for fallback_vfs in self._immediate_fallback_vfs:
1073
1139
            fallback_vfs.check()
1074
1140
 
1075
1141
    def _check_add(self, key, lines, random_id, check_content):
1076
1142
        """check that version_id and lines are safe to add."""
 
1143
        if not all(isinstance(x, bytes) or x is None for x in key):
 
1144
            raise TypeError(key)
1077
1145
        version_id = key[-1]
1078
1146
        if version_id is not None:
1079
1147
            if contains_whitespace(version_id):
1114
1182
        """
1115
1183
        delta_size = 0
1116
1184
        fulltext_size = None
1117
 
        for count in xrange(self._max_delta_chain):
 
1185
        for count in range(self._max_delta_chain):
1118
1186
            try:
1119
1187
                # Note that this only looks in the index of this particular
1120
1188
                # KnitVersionedFiles, not in the fallbacks.  This ensures that
1122
1190
                # boundaries.
1123
1191
                build_details = self._index.get_build_details([parent])
1124
1192
                parent_details = build_details[parent]
1125
 
            except (RevisionNotPresent, KeyError), e:
 
1193
            except (RevisionNotPresent, KeyError) as e:
1126
1194
                # Some basis is not locally present: always fulltext
1127
1195
                return False
1128
1196
            index_memo, compression_parent, _, _ = parent_details
1153
1221
 
1154
1222
        A dict of key to (record_details, index_memo, next, parents) is
1155
1223
        returned.
1156
 
        method is the way referenced data should be applied.
1157
 
        index_memo is the handle to pass to the data access to actually get the
1158
 
            data
1159
 
        next is the build-parent of the version, or None for fulltexts.
1160
 
        parents is the version_ids of the parents of this version
1161
 
 
1162
 
        :param allow_missing: If True do not raise an error on a missing component,
1163
 
            just ignore it.
 
1224
 
 
1225
        * method is the way referenced data should be applied.
 
1226
        * index_memo is the handle to pass to the data access to actually get
 
1227
          the data
 
1228
        * next is the build-parent of the version, or None for fulltexts.
 
1229
        * parents is the version_ids of the parents of this version
 
1230
 
 
1231
        :param allow_missing: If True do not raise an error on a missing
 
1232
            component, just ignore it.
1164
1233
        """
1165
1234
        component_data = {}
1166
1235
        pending_components = keys
1168
1237
            build_details = self._index.get_build_details(pending_components)
1169
1238
            current_components = set(pending_components)
1170
1239
            pending_components = set()
1171
 
            for key, details in build_details.iteritems():
 
1240
            for key, details in viewitems(build_details):
1172
1241
                (index_memo, compression_parent, parents,
1173
1242
                 record_details) = details
1174
 
                method = record_details[0]
1175
1243
                if compression_parent is not None:
1176
1244
                    pending_components.add(compression_parent)
1177
1245
                component_data[key] = self._build_details_to_components(details)
1192
1260
        generator = _VFContentMapGenerator(self, [key])
1193
1261
        return generator._get_content(key)
1194
1262
 
1195
 
    def get_known_graph_ancestry(self, keys):
1196
 
        """Get a KnownGraph instance with the ancestry of keys."""
1197
 
        parent_map, missing_keys = self._index.find_ancestry(keys)
1198
 
        for fallback in self._fallback_vfs:
1199
 
            if not missing_keys:
1200
 
                break
1201
 
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
1202
 
                                                missing_keys)
1203
 
            parent_map.update(f_parent_map)
1204
 
            missing_keys = f_missing_keys
1205
 
        kg = _mod_graph.KnownGraph(parent_map)
1206
 
        return kg
1207
 
 
1208
1263
    def get_parent_map(self, keys):
1209
1264
        """Get a map of the graph parents of keys.
1210
1265
 
1225
1280
            and so on.
1226
1281
        """
1227
1282
        result = {}
1228
 
        sources = [self._index] + self._fallback_vfs
 
1283
        sources = [self._index] + self._immediate_fallback_vfs
1229
1284
        source_results = []
1230
1285
        missing = set(keys)
1231
1286
        for source in sources:
1241
1296
        """Produce a dictionary of knit records.
1242
1297
 
1243
1298
        :return: {key:(record, record_details, digest, next)}
1244
 
            record
1245
 
                data returned from read_records (a KnitContentobject)
1246
 
            record_details
1247
 
                opaque information to pass to parse_record
1248
 
            digest
1249
 
                SHA1 digest of the full text after all steps are done
1250
 
            next
1251
 
                build-parent of the version, i.e. the leftmost ancestor.
 
1299
 
 
1300
            * record: data returned from read_records (a KnitContentobject)
 
1301
            * record_details: opaque information to pass to parse_record
 
1302
            * digest: SHA1 digest of the full text after all steps are done
 
1303
            * next: build-parent of the version, i.e. the leftmost ancestor.
1252
1304
                Will be None if the record is not a delta.
 
1305
 
1253
1306
        :param keys: The keys to build a map for
1254
1307
        :param allow_missing: If some records are missing, rather than
1255
1308
            error, just return the data that could be generated.
1290
1343
                # key = component_id, r = record_details, i_m = index_memo,
1291
1344
                # n = next
1292
1345
                records = [(key, i_m) for key, (r, i_m, n)
1293
 
                                       in position_map.iteritems()]
 
1346
                                       in viewitems(position_map)]
1294
1347
                # Sort by the index memo, so that we request records from the
1295
1348
                # same pack file together, and in forward-sorted order
1296
1349
                records.sort(key=operator.itemgetter(1))
1299
1352
                    (record_details, index_memo, next) = position_map[key]
1300
1353
                    raw_record_map[key] = data, record_details, next
1301
1354
                return raw_record_map
1302
 
            except errors.RetryWithNewPacks, e:
 
1355
            except errors.RetryWithNewPacks as e:
1303
1356
                self._access.reload_or_raise(e)
1304
1357
 
1305
1358
    @classmethod
1325
1378
        prefix_order = []
1326
1379
        for key in keys:
1327
1380
            if len(key) == 1:
1328
 
                prefix = ''
 
1381
                prefix = b''
1329
1382
            else:
1330
1383
                prefix = key[0]
1331
1384
 
1408
1461
                    remaining_keys.discard(content_factory.key)
1409
1462
                    yield content_factory
1410
1463
                return
1411
 
            except errors.RetryWithNewPacks, e:
 
1464
            except errors.RetryWithNewPacks as e:
1412
1465
                self._access.reload_or_raise(e)
1413
1466
 
1414
1467
    def _get_remaining_record_stream(self, keys, ordering,
1421
1474
            # map from key to
1422
1475
            # (record_details, access_memo, compression_parent_key)
1423
1476
            positions = dict((key, self._build_details_to_components(details))
1424
 
                for key, details in build_details.iteritems())
 
1477
                for key, details in viewitems(build_details))
1425
1478
        absent_keys = keys.difference(set(positions))
1426
1479
        # There may be more absent keys : if we're missing the basis component
1427
1480
        # and are trying to include the delta closure.
1525
1578
                        yield KnitContentFactory(key, global_map[key],
1526
1579
                            record_details, None, raw_data, self._factory.annotated, None)
1527
1580
                else:
1528
 
                    vf = self._fallback_vfs[parent_maps.index(source) - 1]
 
1581
                    vf = self._immediate_fallback_vfs[parent_maps.index(source) - 1]
1529
1582
                    for record in vf.get_record_stream(keys, ordering,
1530
1583
                        include_delta_closure):
1531
1584
                        yield record
1535
1588
        missing = set(keys)
1536
1589
        record_map = self._get_record_map(missing, allow_missing=True)
1537
1590
        result = {}
1538
 
        for key, details in record_map.iteritems():
 
1591
        for key, details in viewitems(record_map):
1539
1592
            if key not in missing:
1540
1593
                continue
1541
1594
            # record entry 2 is the 'digest'.
1542
1595
            result[key] = details[2]
1543
1596
        missing.difference_update(set(result))
1544
 
        for source in self._fallback_vfs:
 
1597
        for source in self._immediate_fallback_vfs:
1545
1598
            if not missing:
1546
1599
                break
1547
1600
            new_result = source.get_sha1s(missing)
1572
1625
        else:
1573
1626
            # self is not annotated, but we can strip annotations cheaply.
1574
1627
            annotated = ""
1575
 
            convertibles = set(["knit-annotated-ft-gz"])
 
1628
            convertibles = {"knit-annotated-ft-gz"}
1576
1629
            if self._max_delta_chain:
1577
1630
                delta_types.add("knit-annotated-delta-gz")
1578
1631
                convertibles.add("knit-annotated-delta-gz")
1618
1671
                raise RevisionNotPresent([record.key], self)
1619
1672
            elif ((record.storage_kind in knit_types)
1620
1673
                  and (compression_parent is None
1621
 
                       or not self._fallback_vfs
1622
 
                       or self._index.has_key(compression_parent)
1623
 
                       or not self.has_key(compression_parent))):
 
1674
                       or not self._immediate_fallback_vfs
 
1675
                       or compression_parent in self._index
 
1676
                       or compression_parent not in self)):
1624
1677
                # we can insert the knit record literally if either it has no
1625
1678
                # compression parent OR we already have its basis in this kvf
1626
1679
                # OR the basis is not present even in the fallbacks.  In the
1628
1681
                # will be well, or it won't turn up at all and we'll raise an
1629
1682
                # error at the end.
1630
1683
                #
1631
 
                # TODO: self.has_key is somewhat redundant with
1632
 
                # self._index.has_key; we really want something that directly
 
1684
                # TODO: self.__contains__ is somewhat redundant with
 
1685
                # self._index.__contains__; we really want something that directly
1633
1686
                # asks if it's only present in the fallbacks. -- mbp 20081119
1634
1687
                if record.storage_kind not in native_types:
1635
1688
                    try:
1643
1696
                    # It's a knit record, it has a _raw_record field (even if
1644
1697
                    # it was reconstituted from a network stream).
1645
1698
                    bytes = record._raw_record
1646
 
                options = [record._build_details[0]]
 
1699
                options = [record._build_details[0].encode('ascii')]
1647
1700
                if record._build_details[1]:
1648
 
                    options.append('no-eol')
 
1701
                    options.append(b'no-eol')
1649
1702
                # Just blat it across.
1650
1703
                # Note: This does end up adding data on duplicate keys. As
1651
1704
                # modern repositories use atomic insertions this should not
1657
1710
                access_memo = self._access.add_raw_records(
1658
1711
                    [(record.key, len(bytes))], bytes)[0]
1659
1712
                index_entry = (record.key, options, access_memo, parents)
1660
 
                if 'fulltext' not in options:
 
1713
                if b'fulltext' not in options:
1661
1714
                    # Not a fulltext, so we need to make sure the compression
1662
1715
                    # parent will also be present.
1663
1716
                    # Note that pack backed knits don't need to buffer here
1668
1721
                    #
1669
1722
                    # They're required to be physically in this
1670
1723
                    # KnitVersionedFiles, not in a fallback.
1671
 
                    if not self._index.has_key(compression_parent):
 
1724
                    if compression_parent not in self._index:
1672
1725
                        pending = buffered_index_entries.setdefault(
1673
1726
                            compression_parent, [])
1674
1727
                        pending.append(index_entry)
1767
1820
                # we need key, position, length
1768
1821
                key_records = []
1769
1822
                build_details = self._index.get_build_details(keys)
1770
 
                for key, details in build_details.iteritems():
 
1823
                for key, details in viewitems(build_details):
1771
1824
                    if key in keys:
1772
1825
                        key_records.append((key, details[0]))
1773
1826
                records_iter = enumerate(self._read_records_iter(key_records))
1774
1827
                for (key_idx, (key, data, sha_value)) in records_iter:
1775
 
                    pb.update('Walking content', key_idx, total)
 
1828
                    pb.update(gettext('Walking content'), key_idx, total)
1776
1829
                    compression_parent = build_details[key][1]
1777
1830
                    if compression_parent is None:
1778
1831
                        # fulltext
1790
1843
                    for line in line_iterator:
1791
1844
                        yield line, key
1792
1845
                done = True
1793
 
            except errors.RetryWithNewPacks, e:
 
1846
            except errors.RetryWithNewPacks as e:
1794
1847
                self._access.reload_or_raise(e)
1795
1848
        # If there are still keys we've not yet found, we look in the fallback
1796
1849
        # vfs, and hope to find them there.  Note that if the keys are found
1797
1850
        # but had no changes or no content, the fallback may not return
1798
1851
        # anything.
1799
 
        if keys and not self._fallback_vfs:
 
1852
        if keys and not self._immediate_fallback_vfs:
1800
1853
            # XXX: strictly the second parameter is meant to be the file id
1801
1854
            # but it's not easily accessible here.
1802
1855
            raise RevisionNotPresent(keys, repr(self))
1803
 
        for source in self._fallback_vfs:
 
1856
        for source in self._immediate_fallback_vfs:
1804
1857
            if not keys:
1805
1858
                break
1806
1859
            source_keys = set()
1808
1861
                source_keys.add(key)
1809
1862
                yield line, key
1810
1863
            keys.difference_update(source_keys)
1811
 
        pb.update('Walking content', total, total)
 
1864
        pb.update(gettext('Walking content'), total, total)
1812
1865
 
1813
1866
    def _make_line_delta(self, delta_seq, new_content):
1814
1867
        """Generate a line delta from delta_seq and new_content."""
1849
1902
                    content._lines[j:j+n] = merge_content._lines[i:i+n]
1850
1903
            # XXX: Robert says the following block is a workaround for a
1851
1904
            # now-fixed bug and it can probably be deleted. -- mbp 20080618
1852
 
            if content._lines and content._lines[-1][1][-1] != '\n':
 
1905
            if content._lines and not content._lines[-1][1].endswith(b'\n'):
1853
1906
                # The copied annotation was from a line without a trailing EOL,
1854
1907
                # reinstate one for the content object, to ensure correct
1855
1908
                # serialization.
1856
 
                line = content._lines[-1][1] + '\n'
 
1909
                line = content._lines[-1][1] + b'\n'
1857
1910
                content._lines[-1] = (content._lines[-1][0], line)
1858
1911
        if delta:
1859
1912
            if delta_seq is None:
1879
1932
        :return: the header and the decompressor stream.
1880
1933
                 as (stream, header_record)
1881
1934
        """
1882
 
        df = tuned_gzip.GzipFile(mode='rb', fileobj=StringIO(raw_data))
 
1935
        df = gzip.GzipFile(mode='rb', fileobj=BytesIO(raw_data))
1883
1936
        try:
1884
1937
            # Current serialise
1885
1938
            rec = self._check_header(key, df.readline())
1886
 
        except Exception, e:
 
1939
        except Exception as e:
1887
1940
            raise KnitCorrupt(self,
1888
1941
                              "While reading {%s} got %s(%s)"
1889
1942
                              % (key, e.__class__.__name__, str(e)))
1894
1947
        # 4168 calls in 2880 217 internal
1895
1948
        # 4168 calls to _parse_record_header in 2121
1896
1949
        # 4168 calls to readlines in 330
1897
 
        df = tuned_gzip.GzipFile(mode='rb', fileobj=StringIO(data))
1898
 
        try:
1899
 
            record_contents = df.readlines()
1900
 
        except Exception, e:
1901
 
            raise KnitCorrupt(self, "Corrupt compressed record %r, got %s(%s)" %
1902
 
                (data, e.__class__.__name__, str(e)))
1903
 
        header = record_contents.pop(0)
1904
 
        rec = self._split_header(header)
1905
 
        last_line = record_contents.pop()
1906
 
        if len(record_contents) != int(rec[2]):
1907
 
            raise KnitCorrupt(self,
1908
 
                              'incorrect number of lines %s != %s'
1909
 
                              ' for version {%s} %s'
1910
 
                              % (len(record_contents), int(rec[2]),
1911
 
                                 rec[1], record_contents))
1912
 
        if last_line != 'end %s\n' % rec[1]:
1913
 
            raise KnitCorrupt(self,
1914
 
                              'unexpected version end line %r, wanted %r'
1915
 
                              % (last_line, rec[1]))
1916
 
        df.close()
 
1950
        with gzip.GzipFile(mode='rb', fileobj=BytesIO(data)) as df:
 
1951
            try:
 
1952
                record_contents = df.readlines()
 
1953
            except Exception as e:
 
1954
                raise KnitCorrupt(self, "Corrupt compressed record %r, got %s(%s)" %
 
1955
                    (data, e.__class__.__name__, str(e)))
 
1956
            header = record_contents.pop(0)
 
1957
            rec = self._split_header(header)
 
1958
            last_line = record_contents.pop()
 
1959
            if len(record_contents) != int(rec[2]):
 
1960
                raise KnitCorrupt(self,
 
1961
                                  'incorrect number of lines %s != %s'
 
1962
                                  ' for version {%s} %s'
 
1963
                                  % (len(record_contents), int(rec[2]),
 
1964
                                     rec[1], record_contents))
 
1965
            if last_line != b'end %s\n' % rec[1]:
 
1966
                raise KnitCorrupt(self,
 
1967
                                  'unexpected version end line %r, wanted %r'
 
1968
                                  % (last_line, rec[1]))
1917
1969
        return rec, record_contents
1918
1970
 
1919
1971
    def _read_records_iter(self, records):
1922
1974
        The result will be returned in whatever is the fastest to read.
1923
1975
        Not by the order requested. Also, multiple requests for the same
1924
1976
        record will only yield 1 response.
 
1977
 
1925
1978
        :param records: A list of (key, access_memo) entries
1926
1979
        :return: Yields (key, contents, digest) in the order
1927
1980
                 read, not the order requested
1939
1992
        raw_data = self._access.get_raw_records(
1940
1993
            [index_memo for key, index_memo in needed_records])
1941
1994
 
1942
 
        for (key, index_memo), data in \
1943
 
                izip(iter(needed_records), raw_data):
 
1995
        for (key, index_memo), data in zip(needed_records, raw_data):
1944
1996
            content, digest = self._parse_record(key[-1], data)
1945
1997
            yield key, content, digest
1946
1998
 
1976
2028
            raw_records = self._access.get_raw_records(needed_offsets)
1977
2029
 
1978
2030
        for key, index_memo in records:
1979
 
            data = raw_records.next()
 
2031
            data = next(raw_records)
1980
2032
            yield key, data
1981
2033
 
1982
2034
    def _record_to_data(self, key, digest, lines, dense_lines=None):
1985
2037
        :param key: The key of the record. Currently keys are always serialised
1986
2038
            using just the trailing component.
1987
2039
        :param dense_lines: The bytes of lines but in a denser form. For
1988
 
            instance, if lines is a list of 1000 bytestrings each ending in \n,
1989
 
            dense_lines may be a list with one line in it, containing all the
1990
 
            1000's lines and their \n's. Using dense_lines if it is already
1991
 
            known is a win because the string join to create bytes in this
1992
 
            function spends less time resizing the final string.
1993
 
        :return: (len, a StringIO instance with the raw data ready to read.)
 
2040
            instance, if lines is a list of 1000 bytestrings each ending in
 
2041
            \\n, dense_lines may be a list with one line in it, containing all
 
2042
            the 1000's lines and their \\n's. Using dense_lines if it is
 
2043
            already known is a win because the string join to create bytes in
 
2044
            this function spends less time resizing the final string.
 
2045
        :return: (len, a BytesIO instance with the raw data ready to read.)
1994
2046
        """
1995
 
        chunks = ["version %s %d %s\n" % (key[-1], len(lines), digest)]
 
2047
        chunks = [b"version %s %d %s\n" % (key[-1], len(lines), digest)]
1996
2048
        chunks.extend(dense_lines or lines)
1997
 
        chunks.append("end %s\n" % key[-1])
 
2049
        chunks.append(b"end " + key[-1] + b"\n")
1998
2050
        for chunk in chunks:
1999
 
            if type(chunk) is not str:
 
2051
            if not isinstance(chunk, bytes):
2000
2052
                raise AssertionError(
2001
2053
                    'data must be plain bytes was %s' % type(chunk))
2002
 
        if lines and lines[-1][-1] != '\n':
 
2054
        if lines and not lines[-1].endswith(b'\n'):
2003
2055
            raise ValueError('corrupt lines value %r' % lines)
2004
 
        compressed_bytes = tuned_gzip.chunks_to_gzip(chunks)
 
2056
        compressed_bytes = b''.join(tuned_gzip.chunks_to_gzip(chunks))
2005
2057
        return len(compressed_bytes), compressed_bytes
2006
2058
 
2007
2059
    def _split_header(self, line):
2015
2067
        """See VersionedFiles.keys."""
2016
2068
        if 'evil' in debug.debug_flags:
2017
2069
            trace.mutter_callsite(2, "keys scales with size of history")
2018
 
        sources = [self._index] + self._fallback_vfs
 
2070
        sources = [self._index] + self._immediate_fallback_vfs
2019
2071
        result = set()
2020
2072
        for source in sources:
2021
2073
            result.update(source.keys())
2033
2085
        # Note that _get_content is only called when the _ContentMapGenerator
2034
2086
        # has been constructed with just one key requested for reconstruction.
2035
2087
        if key in self.nonlocal_keys:
2036
 
            record = self.get_record_stream().next()
 
2088
            record = next(self.get_record_stream())
2037
2089
            # Create a content object on the fly
2038
2090
            lines = osutils.chunks_to_lines(record.get_bytes_as('chunked'))
2039
2091
            return PlainKnitContent(lines, record.key)
2061
2113
 
2062
2114
        missing_keys = set(nonlocal_keys)
2063
2115
        # Read from remote versioned file instances and provide to our caller.
2064
 
        for source in self.vf._fallback_vfs:
 
2116
        for source in self.vf._immediate_fallback_vfs:
2065
2117
            if not missing_keys:
2066
2118
                break
2067
2119
            # Loop over fallback repositories asking them for texts - ignore
2156
2208
        """
2157
2209
        lines = []
2158
2210
        # kind marker for dispatch on the far side,
2159
 
        lines.append('knit-delta-closure')
 
2211
        lines.append(b'knit-delta-closure')
2160
2212
        # Annotated or not
2161
2213
        if self.vf._factory.annotated:
2162
 
            lines.append('annotated')
 
2214
            lines.append(b'annotated')
2163
2215
        else:
2164
 
            lines.append('')
 
2216
            lines.append(b'')
2165
2217
        # then the list of keys
2166
 
        lines.append('\t'.join(['\x00'.join(key) for key in self.keys
2167
 
            if key not in self.nonlocal_keys]))
 
2218
        lines.append(b'\t'.join(b'\x00'.join(key) for key in self.keys
 
2219
            if key not in self.nonlocal_keys))
2168
2220
        # then the _raw_record_map in serialised form:
2169
2221
        map_byte_list = []
2170
2222
        # for each item in the map:
2175
2227
        # one line with next ('' for None)
2176
2228
        # one line with byte count of the record bytes
2177
2229
        # the record bytes
2178
 
        for key, (record_bytes, (method, noeol), next) in \
2179
 
            self._raw_record_map.iteritems():
2180
 
            key_bytes = '\x00'.join(key)
 
2230
        for key, (record_bytes, (method, noeol), next) in viewitems(
 
2231
                self._raw_record_map):
 
2232
            key_bytes = b'\x00'.join(key)
2181
2233
            parents = self.global_map.get(key, None)
2182
2234
            if parents is None:
2183
 
                parent_bytes = 'None:'
 
2235
                parent_bytes = b'None:'
2184
2236
            else:
2185
 
                parent_bytes = '\t'.join('\x00'.join(key) for key in parents)
2186
 
            method_bytes = method
 
2237
                parent_bytes = b'\t'.join(b'\x00'.join(key) for key in parents)
 
2238
            method_bytes = method.encode('ascii')
2187
2239
            if noeol:
2188
 
                noeol_bytes = "T"
 
2240
                noeol_bytes = b"T"
2189
2241
            else:
2190
 
                noeol_bytes = "F"
 
2242
                noeol_bytes = b"F"
2191
2243
            if next:
2192
 
                next_bytes = '\x00'.join(next)
 
2244
                next_bytes = b'\x00'.join(next)
2193
2245
            else:
2194
 
                next_bytes = ''
2195
 
            map_byte_list.append('%s\n%s\n%s\n%s\n%s\n%d\n%s' % (
2196
 
                key_bytes, parent_bytes, method_bytes, noeol_bytes, next_bytes,
2197
 
                len(record_bytes), record_bytes))
2198
 
        map_bytes = ''.join(map_byte_list)
 
2246
                next_bytes = b''
 
2247
            map_byte_list.append(b'\n'.join(
 
2248
                [key_bytes, parent_bytes, method_bytes, noeol_bytes, next_bytes,
 
2249
                 b'%d' % len(record_bytes), record_bytes]))
 
2250
        map_bytes = b''.join(map_byte_list)
2199
2251
        lines.append(map_bytes)
2200
 
        bytes = '\n'.join(lines)
 
2252
        bytes = b'\n'.join(lines)
2201
2253
        return bytes
2202
2254
 
2203
2255
 
2262
2314
        self.vf = KnitVersionedFiles(None, None)
2263
2315
        start = line_end
2264
2316
        # Annotated or not
2265
 
        line_end = bytes.find('\n', start)
 
2317
        line_end = bytes.find(b'\n', start)
2266
2318
        line = bytes[start:line_end]
2267
2319
        start = line_end + 1
2268
 
        if line == 'annotated':
 
2320
        if line == b'annotated':
2269
2321
            self._factory = KnitAnnotateFactory()
2270
2322
        else:
2271
2323
            self._factory = KnitPlainFactory()
2272
2324
        # list of keys to emit in get_record_stream
2273
 
        line_end = bytes.find('\n', start)
 
2325
        line_end = bytes.find(b'\n', start)
2274
2326
        line = bytes[start:line_end]
2275
2327
        start = line_end + 1
2276
2328
        self.keys = [
2277
 
            tuple(segment.split('\x00')) for segment in line.split('\t')
 
2329
            tuple(segment.split(b'\x00')) for segment in line.split(b'\t')
2278
2330
            if segment]
2279
2331
        # now a loop until the end. XXX: It would be nice if this was just a
2280
2332
        # bunch of the same records as get_record_stream(..., False) gives, but
2282
2334
        end = len(bytes)
2283
2335
        while start < end:
2284
2336
            # 1 line with key
2285
 
            line_end = bytes.find('\n', start)
2286
 
            key = tuple(bytes[start:line_end].split('\x00'))
 
2337
            line_end = bytes.find(b'\n', start)
 
2338
            key = tuple(bytes[start:line_end].split(b'\x00'))
2287
2339
            start = line_end + 1
2288
2340
            # 1 line with parents (None: for None, '' for ())
2289
 
            line_end = bytes.find('\n', start)
 
2341
            line_end = bytes.find(b'\n', start)
2290
2342
            line = bytes[start:line_end]
2291
 
            if line == 'None:':
 
2343
            if line == b'None:':
2292
2344
                parents = None
2293
2345
            else:
2294
2346
                parents = tuple(
2295
 
                    [tuple(segment.split('\x00')) for segment in line.split('\t')
2296
 
                     if segment])
 
2347
                    tuple(segment.split(b'\x00')) for segment in line.split(b'\t')
 
2348
                     if segment)
2297
2349
            self.global_map[key] = parents
2298
2350
            start = line_end + 1
2299
2351
            # one line with method
2300
 
            line_end = bytes.find('\n', start)
 
2352
            line_end = bytes.find(b'\n', start)
2301
2353
            line = bytes[start:line_end]
2302
 
            method = line
 
2354
            method = line.decode('ascii')
2303
2355
            start = line_end + 1
2304
2356
            # one line with noeol
2305
 
            line_end = bytes.find('\n', start)
 
2357
            line_end = bytes.find(b'\n', start)
2306
2358
            line = bytes[start:line_end]
2307
 
            noeol = line == "T"
 
2359
            noeol = line == b"T"
2308
2360
            start = line_end + 1
2309
 
            # one line with next ('' for None)
2310
 
            line_end = bytes.find('\n', start)
 
2361
            # one line with next (b'' for None)
 
2362
            line_end = bytes.find(b'\n', start)
2311
2363
            line = bytes[start:line_end]
2312
2364
            if not line:
2313
2365
                next = None
2314
2366
            else:
2315
 
                next = tuple(bytes[start:line_end].split('\x00'))
 
2367
                next = tuple(bytes[start:line_end].split(b'\x00'))
2316
2368
            start = line_end + 1
2317
2369
            # one line with byte count of the record bytes
2318
 
            line_end = bytes.find('\n', start)
 
2370
            line_end = bytes.find(b'\n', start)
2319
2371
            line = bytes[start:line_end]
2320
2372
            count = int(line)
2321
2373
            start = line_end + 1
2395
2447
        ABI change with the C extension that reads .kndx files.
2396
2448
    """
2397
2449
 
2398
 
    HEADER = "# bzr knit index 8\n"
 
2450
    HEADER = b"# bzr knit index 8\n"
2399
2451
 
2400
2452
    def __init__(self, transport, mapper, get_scope, allow_writes, is_locked):
2401
2453
        """Create a _KndxIndex on transport using mapper."""
2440
2492
 
2441
2493
            try:
2442
2494
                for key, options, (_, pos, size), parents in path_keys:
 
2495
                    if not all(isinstance(option, bytes) for option in options):
 
2496
                        raise TypeError(options)
2443
2497
                    if parents is None:
2444
2498
                        # kndx indices cannot be parentless.
2445
2499
                        parents = ()
2446
 
                    line = "\n%s %s %s %s %s :" % (
2447
 
                        key[-1], ','.join(options), pos, size,
2448
 
                        self._dictionary_compress(parents))
2449
 
                    if type(line) is not str:
 
2500
                    line = b' '.join([
 
2501
                        b'\n' + key[-1], b','.join(options), b'%d' % pos, b'%d' % size,
 
2502
                        self._dictionary_compress(parents), b':'])
 
2503
                    if not isinstance(line, bytes):
2450
2504
                        raise AssertionError(
2451
2505
                            'data must be utf8 was %s' % type(line))
2452
2506
                    lines.append(line)
2453
2507
                    self._cache_key(key, options, pos, size, parents)
2454
2508
                if len(orig_history):
2455
 
                    self._transport.append_bytes(path, ''.join(lines))
 
2509
                    self._transport.append_bytes(path, b''.join(lines))
2456
2510
                else:
2457
2511
                    self._init_index(path, lines)
2458
2512
            except:
2504
2558
 
2505
2559
    def check_header(self, fp):
2506
2560
        line = fp.readline()
2507
 
        if line == '':
 
2561
        if line == b'':
2508
2562
            # An empty file can actually be treated as though the file doesn't
2509
2563
            # exist yet.
2510
2564
            raise errors.NoSuchFile(self)
2551
2605
            if key not in parent_map:
2552
2606
                continue # Ghost
2553
2607
            method = self.get_method(key)
 
2608
            if not isinstance(method, str):
 
2609
                raise TypeError(method)
2554
2610
            parents = parent_map[key]
2555
2611
            if method == 'fulltext':
2556
2612
                compression_parent = None
2557
2613
            else:
2558
2614
                compression_parent = parents[0]
2559
 
            noeol = 'no-eol' in self.get_options(key)
 
2615
            noeol = b'no-eol' in self.get_options(key)
2560
2616
            index_memo = self.get_position(key)
2561
2617
            result[key] = (index_memo, compression_parent,
2562
2618
                                  parents, (method, noeol))
2565
2621
    def get_method(self, key):
2566
2622
        """Return compression method of specified key."""
2567
2623
        options = self.get_options(key)
2568
 
        if 'fulltext' in options:
 
2624
        if b'fulltext' in options:
2569
2625
            return 'fulltext'
2570
 
        elif 'line-delta' in options:
 
2626
        elif b'line-delta' in options:
2571
2627
            return 'line-delta'
2572
2628
        else:
2573
 
            raise errors.KnitIndexUnknownMethod(self, options)
 
2629
            raise KnitIndexUnknownMethod(self, options)
2574
2630
 
2575
2631
    def get_options(self, key):
2576
2632
        """Return a list representing options.
2646
2702
        entry = self._kndx_cache[prefix][0][suffix]
2647
2703
        return key, entry[2], entry[3]
2648
2704
 
2649
 
    has_key = _mod_index._has_key_from_parent_map
 
2705
    __contains__ = _mod_index._has_key_from_parent_map
2650
2706
 
2651
2707
    def _init_index(self, path, extra_lines=[]):
2652
2708
        """Initialize an index."""
2653
 
        sio = StringIO()
 
2709
        sio = BytesIO()
2654
2710
        sio.write(self.HEADER)
2655
2711
        sio.writelines(extra_lines)
2656
2712
        sio.seek(0)
2668
2724
        result = set()
2669
2725
        # Identify all key prefixes.
2670
2726
        # XXX: A bit hacky, needs polish.
2671
 
        if type(self._mapper) is ConstantMapper:
 
2727
        if isinstance(self._mapper, ConstantMapper):
2672
2728
            prefixes = [()]
2673
2729
        else:
2674
2730
            relpaths = set()
2693
2749
                self._filename = prefix
2694
2750
                try:
2695
2751
                    path = self._mapper.map(prefix) + '.kndx'
2696
 
                    fp = self._transport.get(path)
2697
 
                    try:
 
2752
                    with self._transport.get(path) as fp:
2698
2753
                        # _load_data may raise NoSuchFile if the target knit is
2699
2754
                        # completely empty.
2700
2755
                        _load_data(self, fp)
2701
 
                    finally:
2702
 
                        fp.close()
2703
2756
                    self._kndx_cache[prefix] = (self._cache, self._history)
2704
2757
                    del self._cache
2705
2758
                    del self._filename
2706
2759
                    del self._history
2707
2760
                except NoSuchFile:
2708
2761
                    self._kndx_cache[prefix] = ({}, [])
2709
 
                    if type(self._mapper) is ConstantMapper:
 
2762
                    if isinstance(self._mapper, ConstantMapper):
2710
2763
                        # preserve behaviour for revisions.kndx etc.
2711
2764
                        self._init_index(path)
2712
2765
                    del self._cache
2732
2785
            '.' prefix.
2733
2786
        """
2734
2787
        if not keys:
2735
 
            return ''
 
2788
            return b''
2736
2789
        result_list = []
2737
2790
        prefix = keys[0][:-1]
2738
2791
        cache = self._kndx_cache[prefix][0]
2742
2795
                raise ValueError("mismatched prefixes for %r" % keys)
2743
2796
            if key[-1] in cache:
2744
2797
                # -- inlined lookup() --
2745
 
                result_list.append(str(cache[key[-1]][5]))
 
2798
                result_list.append(b'%d' % cache[key[-1]][5])
2746
2799
                # -- end lookup () --
2747
2800
            else:
2748
 
                result_list.append('.' + key[-1])
2749
 
        return ' '.join(result_list)
 
2801
                result_list.append(b'.' + key[-1])
 
2802
        return b' '.join(result_list)
2750
2803
 
2751
2804
    def _reset_cache(self):
2752
2805
        # Possibly this should be a LRU cache. A dictionary from key_prefix to
2783
2836
 
2784
2837
    def _split_key(self, key):
2785
2838
        """Split key into a prefix and suffix."""
 
2839
        # GZ 2018-07-03: This is intentionally either a sequence or bytes?
 
2840
        if isinstance(key, bytes):
 
2841
            return key[:-1], key[-1:]
2786
2842
        return key[:-1], key[-1]
2787
2843
 
2788
2844
 
2789
 
class _KeyRefs(object):
2790
 
 
2791
 
    def __init__(self, track_new_keys=False):
2792
 
        # dict mapping 'key' to 'set of keys referring to that key'
2793
 
        self.refs = {}
2794
 
        if track_new_keys:
2795
 
            # set remembering all new keys
2796
 
            self.new_keys = set()
2797
 
        else:
2798
 
            self.new_keys = None
2799
 
 
2800
 
    def clear(self):
2801
 
        if self.refs:
2802
 
            self.refs.clear()
2803
 
        if self.new_keys:
2804
 
            self.new_keys.clear()
2805
 
 
2806
 
    def add_references(self, key, refs):
2807
 
        # Record the new references
2808
 
        for referenced in refs:
2809
 
            try:
2810
 
                needed_by = self.refs[referenced]
2811
 
            except KeyError:
2812
 
                needed_by = self.refs[referenced] = set()
2813
 
            needed_by.add(key)
2814
 
        # Discard references satisfied by the new key
2815
 
        self.add_key(key)
2816
 
 
2817
 
    def get_new_keys(self):
2818
 
        return self.new_keys
2819
 
    
2820
 
    def get_unsatisfied_refs(self):
2821
 
        return self.refs.iterkeys()
2822
 
 
2823
 
    def _satisfy_refs_for_key(self, key):
2824
 
        try:
2825
 
            del self.refs[key]
2826
 
        except KeyError:
2827
 
            # No keys depended on this key.  That's ok.
2828
 
            pass
2829
 
 
2830
 
    def add_key(self, key):
2831
 
        # satisfy refs for key, and remember that we've seen this key.
2832
 
        self._satisfy_refs_for_key(key)
2833
 
        if self.new_keys is not None:
2834
 
            self.new_keys.add(key)
2835
 
 
2836
 
    def satisfy_refs_for_keys(self, keys):
2837
 
        for key in keys:
2838
 
            self._satisfy_refs_for_key(key)
2839
 
 
2840
 
    def get_referrers(self):
2841
 
        result = set()
2842
 
        for referrers in self.refs.itervalues():
2843
 
            result.update(referrers)
2844
 
        return result
2845
 
 
2846
 
 
2847
2845
class _KnitGraphIndex(object):
2848
2846
    """A KnitVersionedFiles index layered on GraphIndex."""
2849
2847
 
2851
2849
        add_callback=None, track_external_parent_refs=False):
2852
2850
        """Construct a KnitGraphIndex on a graph_index.
2853
2851
 
2854
 
        :param graph_index: An implementation of bzrlib.index.GraphIndex.
 
2852
        :param graph_index: An implementation of breezy.index.GraphIndex.
2855
2853
        :param is_locked: A callback to check whether the object should answer
2856
2854
            queries.
2857
2855
        :param deltas: Allow delta-compressed records.
2918
2916
                if key_dependencies is not None:
2919
2917
                    key_dependencies.add_references(key, parents)
2920
2918
            index, pos, size = access_memo
2921
 
            if 'no-eol' in options:
2922
 
                value = 'N'
 
2919
            if b'no-eol' in options:
 
2920
                value = b'N'
2923
2921
            else:
2924
 
                value = ' '
2925
 
            value += "%d %d" % (pos, size)
 
2922
                value = b' '
 
2923
            value += b"%d %d" % (pos, size)
2926
2924
            if not self._deltas:
2927
 
                if 'line-delta' in options:
 
2925
                if b'line-delta' in options:
2928
2926
                    raise KnitCorrupt(self, "attempt to add line-delta in non-delta knit")
2929
2927
            if self._parents:
2930
2928
                if self._deltas:
2931
 
                    if 'line-delta' in options:
 
2929
                    if b'line-delta' in options:
2932
2930
                        node_refs = (parents, (parents[0],))
2933
2931
                        if missing_compression_parents:
2934
2932
                            compression_parents.add(parents[0])
2950
2948
                # Sometimes these are passed as a list rather than a tuple
2951
2949
                passed = static_tuple.as_tuples(keys[key])
2952
2950
                passed_parents = passed[1][:1]
2953
 
                if (value[0] != keys[key][0][0] or
 
2951
                if (value[0:1] != keys[key][0][0:1] or
2954
2952
                    parents != passed_parents):
2955
2953
                    node_refs = static_tuple.as_tuples(node_refs)
2956
2954
                    raise KnitCorrupt(self, "inconsistent details in add_records"
2958
2956
                del keys[key]
2959
2957
        result = []
2960
2958
        if self._parents:
2961
 
            for key, (value, node_refs) in keys.iteritems():
 
2959
            for key, (value, node_refs) in viewitems(keys):
2962
2960
                result.append((key, value, node_refs))
2963
2961
        else:
2964
 
            for key, (value, node_refs) in keys.iteritems():
 
2962
            for key, (value, node_refs) in viewitems(keys):
2965
2963
                result.append((key, value))
2966
2964
        self._add_callback(result)
2967
2965
        if missing_compression_parents:
3065
3063
                compression_parent_key = None
3066
3064
            else:
3067
3065
                compression_parent_key = self._compression_parent(entry)
3068
 
            noeol = (entry[2][0] == 'N')
 
3066
            noeol = (entry[2][0:1] == b'N')
3069
3067
            if compression_parent_key:
3070
3068
                method = 'line-delta'
3071
3069
            else:
3120
3118
        e.g. ['foo', 'bar']
3121
3119
        """
3122
3120
        node = self._get_node(key)
3123
 
        options = [self._get_method(node)]
3124
 
        if node[2][0] == 'N':
3125
 
            options.append('no-eol')
 
3121
        options = [self._get_method(node).encode('ascii')]
 
3122
        if node[2][0:1] == b'N':
 
3123
            options.append(b'no-eol')
3126
3124
        return options
3127
3125
 
3128
3126
    def find_ancestry(self, keys):
3156
3154
        node = self._get_node(key)
3157
3155
        return self._node_to_position(node)
3158
3156
 
3159
 
    has_key = _mod_index._has_key_from_parent_map
 
3157
    __contains__ = _mod_index._has_key_from_parent_map
3160
3158
 
3161
3159
    def keys(self):
3162
3160
        """Get all the keys in the collection.
3170
3168
 
3171
3169
    def _node_to_position(self, node):
3172
3170
        """Convert an index value to position details."""
3173
 
        bits = node[2][1:].split(' ')
 
3171
        bits = node[2][1:].split(b' ')
3174
3172
        return node[0], int(bits[0]), int(bits[1])
3175
3173
 
3176
3174
    def _sort_keys_by_io(self, keys, positions):
3221
3219
            opaque index memo. For _KnitKeyAccess the memo is (key, pos,
3222
3220
            length), where the key is the record key.
3223
3221
        """
3224
 
        if type(raw_data) is not str:
 
3222
        if not isinstance(raw_data, bytes):
3225
3223
            raise AssertionError(
3226
3224
                'data must be plain bytes was %s' % type(raw_data))
3227
3225
        result = []
3278
3276
                yield data
3279
3277
 
3280
3278
 
3281
 
class _DirectPackAccess(object):
3282
 
    """Access to data in one or more packs with less translation."""
3283
 
 
3284
 
    def __init__(self, index_to_packs, reload_func=None, flush_func=None):
3285
 
        """Create a _DirectPackAccess object.
3286
 
 
3287
 
        :param index_to_packs: A dict mapping index objects to the transport
3288
 
            and file names for obtaining data.
3289
 
        :param reload_func: A function to call if we determine that the pack
3290
 
            files have moved and we need to reload our caches. See
3291
 
            bzrlib.repo_fmt.pack_repo.AggregateIndex for more details.
3292
 
        """
3293
 
        self._container_writer = None
3294
 
        self._write_index = None
3295
 
        self._indices = index_to_packs
3296
 
        self._reload_func = reload_func
3297
 
        self._flush_func = flush_func
3298
 
 
3299
 
    def add_raw_records(self, key_sizes, raw_data):
3300
 
        """Add raw knit bytes to a storage area.
3301
 
 
3302
 
        The data is spooled to the container writer in one bytes-record per
3303
 
        raw data item.
3304
 
 
3305
 
        :param sizes: An iterable of tuples containing the key and size of each
3306
 
            raw data segment.
3307
 
        :param raw_data: A bytestring containing the data.
3308
 
        :return: A list of memos to retrieve the record later. Each memo is an
3309
 
            opaque index memo. For _DirectPackAccess the memo is (index, pos,
3310
 
            length), where the index field is the write_index object supplied
3311
 
            to the PackAccess object.
3312
 
        """
3313
 
        if type(raw_data) is not str:
3314
 
            raise AssertionError(
3315
 
                'data must be plain bytes was %s' % type(raw_data))
3316
 
        result = []
3317
 
        offset = 0
3318
 
        for key, size in key_sizes:
3319
 
            p_offset, p_length = self._container_writer.add_bytes_record(
3320
 
                raw_data[offset:offset+size], [])
3321
 
            offset += size
3322
 
            result.append((self._write_index, p_offset, p_length))
3323
 
        return result
3324
 
 
3325
 
    def flush(self):
3326
 
        """Flush pending writes on this access object.
3327
 
 
3328
 
        This will flush any buffered writes to a NewPack.
3329
 
        """
3330
 
        if self._flush_func is not None:
3331
 
            self._flush_func()
3332
 
            
3333
 
    def get_raw_records(self, memos_for_retrieval):
3334
 
        """Get the raw bytes for a records.
3335
 
 
3336
 
        :param memos_for_retrieval: An iterable containing the (index, pos,
3337
 
            length) memo for retrieving the bytes. The Pack access method
3338
 
            looks up the pack to use for a given record in its index_to_pack
3339
 
            map.
3340
 
        :return: An iterator over the bytes of the records.
3341
 
        """
3342
 
        # first pass, group into same-index requests
3343
 
        request_lists = []
3344
 
        current_index = None
3345
 
        for (index, offset, length) in memos_for_retrieval:
3346
 
            if current_index == index:
3347
 
                current_list.append((offset, length))
3348
 
            else:
3349
 
                if current_index is not None:
3350
 
                    request_lists.append((current_index, current_list))
3351
 
                current_index = index
3352
 
                current_list = [(offset, length)]
3353
 
        # handle the last entry
3354
 
        if current_index is not None:
3355
 
            request_lists.append((current_index, current_list))
3356
 
        for index, offsets in request_lists:
3357
 
            try:
3358
 
                transport, path = self._indices[index]
3359
 
            except KeyError:
3360
 
                # A KeyError here indicates that someone has triggered an index
3361
 
                # reload, and this index has gone missing, we need to start
3362
 
                # over.
3363
 
                if self._reload_func is None:
3364
 
                    # If we don't have a _reload_func there is nothing that can
3365
 
                    # be done
3366
 
                    raise
3367
 
                raise errors.RetryWithNewPacks(index,
3368
 
                                               reload_occurred=True,
3369
 
                                               exc_info=sys.exc_info())
3370
 
            try:
3371
 
                reader = pack.make_readv_reader(transport, path, offsets)
3372
 
                for names, read_func in reader.iter_records():
3373
 
                    yield read_func(None)
3374
 
            except errors.NoSuchFile:
3375
 
                # A NoSuchFile error indicates that a pack file has gone
3376
 
                # missing on disk, we need to trigger a reload, and start over.
3377
 
                if self._reload_func is None:
3378
 
                    raise
3379
 
                raise errors.RetryWithNewPacks(transport.abspath(path),
3380
 
                                               reload_occurred=False,
3381
 
                                               exc_info=sys.exc_info())
3382
 
 
3383
 
    def set_writer(self, writer, index, transport_packname):
3384
 
        """Set a writer to use for adding data."""
3385
 
        if index is not None:
3386
 
            self._indices[index] = transport_packname
3387
 
        self._container_writer = writer
3388
 
        self._write_index = index
3389
 
 
3390
 
    def reload_or_raise(self, retry_exc):
3391
 
        """Try calling the reload function, or re-raise the original exception.
3392
 
 
3393
 
        This should be called after _DirectPackAccess raises a
3394
 
        RetryWithNewPacks exception. This function will handle the common logic
3395
 
        of determining when the error is fatal versus being temporary.
3396
 
        It will also make sure that the original exception is raised, rather
3397
 
        than the RetryWithNewPacks exception.
3398
 
 
3399
 
        If this function returns, then the calling function should retry
3400
 
        whatever operation was being performed. Otherwise an exception will
3401
 
        be raised.
3402
 
 
3403
 
        :param retry_exc: A RetryWithNewPacks exception.
3404
 
        """
3405
 
        is_error = False
3406
 
        if self._reload_func is None:
3407
 
            is_error = True
3408
 
        elif not self._reload_func():
3409
 
            # The reload claimed that nothing changed
3410
 
            if not retry_exc.reload_occurred:
3411
 
                # If there wasn't an earlier reload, then we really were
3412
 
                # expecting to find changes. We didn't find them, so this is a
3413
 
                # hard error
3414
 
                is_error = True
3415
 
        if is_error:
3416
 
            exc_class, exc_value, exc_traceback = retry_exc.exc_info
3417
 
            raise exc_class, exc_value, exc_traceback
3418
 
 
3419
 
 
3420
 
# Deprecated, use PatienceSequenceMatcher instead
3421
 
KnitSequenceMatcher = patiencediff.PatienceSequenceMatcher
3422
 
 
3423
 
 
3424
3279
def annotate_knit(knit, revision_id):
3425
3280
    """Annotate a knit with no cached annotations.
3426
3281
 
3469
3324
            passing to read_records_iter to start reading in the raw data from
3470
3325
            the pack file.
3471
3326
        """
3472
 
        pending = set([key])
 
3327
        pending = {key}
3473
3328
        records = []
3474
3329
        ann_keys = set()
3475
3330
        self._num_needed_children[key] = 1
3480
3335
            self._all_build_details.update(build_details)
3481
3336
            # new_nodes = self._vf._index._get_entries(this_iteration)
3482
3337
            pending = set()
3483
 
            for key, details in build_details.iteritems():
 
3338
            for key, details in viewitems(build_details):
3484
3339
                (index_memo, compression_parent, parent_keys,
3485
3340
                 record_details) = details
3486
3341
                self._parent_map[key] = parent_keys
3501
3356
                    else:
3502
3357
                        self._num_compression_children[compression_parent] = 1
3503
3358
 
3504
 
            missing_versions = this_iteration.difference(build_details.keys())
 
3359
            missing_versions = this_iteration.difference(build_details)
3505
3360
            if missing_versions:
3506
3361
                for key in missing_versions:
3507
3362
                    if key in self._parent_map and key in self._text_cache:
3524
3379
        return records, ann_keys
3525
3380
 
3526
3381
    def _get_needed_texts(self, key, pb=None):
3527
 
        # if True or len(self._vf._fallback_vfs) > 0:
3528
 
        if len(self._vf._fallback_vfs) > 0:
 
3382
        # if True or len(self._vf._immediate_fallback_vfs) > 0:
 
3383
        if len(self._vf._immediate_fallback_vfs) > 0:
3529
3384
            # If we have fallbacks, go to the generic path
3530
3385
            for v in annotate.Annotator._get_needed_texts(self, key, pb=pb):
3531
3386
                yield v
3536
3391
                for idx, (sub_key, text, num_lines) in enumerate(
3537
3392
                                                self._extract_texts(records)):
3538
3393
                    if pb is not None:
3539
 
                        pb.update('annotating', idx, len(records))
 
3394
                        pb.update(gettext('annotating'), idx, len(records))
3540
3395
                    yield sub_key, text, num_lines
3541
3396
                for sub_key in ann_keys:
3542
3397
                    text = self._text_cache[sub_key]
3543
3398
                    num_lines = len(text) # bad assumption
3544
3399
                    yield sub_key, text, num_lines
3545
3400
                return
3546
 
            except errors.RetryWithNewPacks, e:
 
3401
            except errors.RetryWithNewPacks as e:
3547
3402
                self._vf._access.reload_or_raise(e)
3548
3403
                # The cached build_details are no longer valid
3549
3404
                self._all_build_details.clear()
3708
3563
                    to_process.extend(self._process_pending(key))
3709
3564
 
3710
3565
try:
3711
 
    from bzrlib._knit_load_data_pyx import _load_data_c as _load_data
3712
 
except ImportError, e:
 
3566
    from ._knit_load_data_pyx import _load_data_c as _load_data
 
3567
except ImportError as e:
3713
3568
    osutils.failed_to_load_extension(e)
3714
 
    from bzrlib._knit_load_data_py import _load_data_py as _load_data
 
3569
    from ._knit_load_data_py import _load_data_py as _load_data