/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/knit.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

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