/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
 
127
126
 
128
127
DATA_SUFFIX = '.knit'
129
128
INDEX_SUFFIX = '.kndx'
130
 
_STREAM_MIN_BUFFER_SIZE = 5 * 1024 * 1024
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
 
129
_STREAM_MIN_BUFFER_SIZE = 5*1024*1024
206
130
 
207
131
 
208
132
class KnitAdapter(object):
228
152
        rec, contents = \
229
153
            self._data._parse_record_unchecked(annotated_compressed_bytes)
230
154
        content = self._annotate_factory.parse_fulltext(contents, rec[1])
231
 
        size, bytes = self._data._record_to_data(
232
 
            (rec[1],), rec[3], content.text())
 
155
        size, bytes = self._data._record_to_data((rec[1],), rec[3], content.text())
233
156
        return bytes
234
157
 
235
158
 
241
164
        rec, contents = \
242
165
            self._data._parse_record_unchecked(annotated_compressed_bytes)
243
166
        delta = self._annotate_factory.parse_line_delta(contents, rec[1],
244
 
                                                        plain=True)
 
167
            plain=True)
245
168
        contents = self._plain_factory.lower_line_delta(delta)
246
169
        size, bytes = self._data._record_to_data((rec[1],), rec[3], contents)
247
170
        return bytes
255
178
        rec, contents = \
256
179
            self._data._parse_record_unchecked(annotated_compressed_bytes)
257
180
        content, delta = self._annotate_factory.parse_record(factory.key[-1],
258
 
                                                             contents, factory._build_details, None)
259
 
        return b''.join(content.text())
 
181
            contents, factory._build_details, None)
 
182
        return ''.join(content.text())
260
183
 
261
184
 
262
185
class DeltaAnnotatedToFullText(KnitAdapter):
267
190
        rec, contents = \
268
191
            self._data._parse_record_unchecked(annotated_compressed_bytes)
269
192
        delta = self._annotate_factory.parse_line_delta(contents, rec[1],
270
 
                                                        plain=True)
 
193
            plain=True)
271
194
        compression_parent = factory.parents[0]
272
 
        basis_entry = next(self._basis_vf.get_record_stream(
273
 
            [compression_parent], 'unordered', True))
 
195
        basis_entry = self._basis_vf.get_record_stream(
 
196
            [compression_parent], 'unordered', True).next()
274
197
        if basis_entry.storage_kind == 'absent':
275
198
            raise errors.RevisionNotPresent(compression_parent, self._basis_vf)
276
199
        basis_chunks = basis_entry.get_bytes_as('chunked')
280
203
        basis_content = PlainKnitContent(basis_lines, compression_parent)
281
204
        basis_content.apply_delta(delta, rec[1])
282
205
        basis_content._should_strip_eol = factory._build_details[1]
283
 
        return b''.join(basis_content.text())
 
206
        return ''.join(basis_content.text())
284
207
 
285
208
 
286
209
class FTPlainToFullText(KnitAdapter):
291
214
        rec, contents = \
292
215
            self._data._parse_record_unchecked(compressed_bytes)
293
216
        content, delta = self._plain_factory.parse_record(factory.key[-1],
294
 
                                                          contents, factory._build_details, None)
295
 
        return b''.join(content.text())
 
217
            contents, factory._build_details, None)
 
218
        return ''.join(content.text())
296
219
 
297
220
 
298
221
class DeltaPlainToFullText(KnitAdapter):
305
228
        delta = self._plain_factory.parse_line_delta(contents, rec[1])
306
229
        compression_parent = factory.parents[0]
307
230
        # XXX: string splitting overhead.
308
 
        basis_entry = next(self._basis_vf.get_record_stream(
309
 
            [compression_parent], 'unordered', True))
 
231
        basis_entry = self._basis_vf.get_record_stream(
 
232
            [compression_parent], 'unordered', True).next()
310
233
        if basis_entry.storage_kind == 'absent':
311
234
            raise errors.RevisionNotPresent(compression_parent, self._basis_vf)
312
235
        basis_chunks = basis_entry.get_bytes_as('chunked')
315
238
        # Manually apply the delta because we have one annotated content and
316
239
        # one plain.
317
240
        content, _ = self._plain_factory.parse_record(rec[1], contents,
318
 
                                                      factory._build_details, basis_content)
319
 
        return b''.join(content.text())
 
241
            factory._build_details, basis_content)
 
242
        return ''.join(content.text())
320
243
 
321
244
 
322
245
class KnitContentFactory(ContentFactory):
326
249
    """
327
250
 
328
251
    def __init__(self, key, parents, build_details, sha1, raw_record,
329
 
                 annotated, knit=None, network_bytes=None):
 
252
        annotated, knit=None, network_bytes=None):
330
253
        """Create a KnitContentFactory for key.
331
254
 
332
255
        :param key: The key.
360
283
    def _create_network_bytes(self):
361
284
        """Create a fully serialised network version for transmission."""
362
285
        # storage_kind, key, parents, Noeol, raw_record
363
 
        key_bytes = b'\x00'.join(self.key)
 
286
        key_bytes = '\x00'.join(self.key)
364
287
        if self.parents is None:
365
 
            parent_bytes = b'None:'
 
288
            parent_bytes = 'None:'
366
289
        else:
367
 
            parent_bytes = b'\t'.join(b'\x00'.join(key)
368
 
                                      for key in self.parents)
 
290
            parent_bytes = '\t'.join('\x00'.join(key) for key in self.parents)
369
291
        if self._build_details[1]:
370
 
            noeol = b'N'
 
292
            noeol = 'N'
371
293
        else:
372
 
            noeol = b' '
373
 
        network_bytes = b"%s\n%s\n%s\n%s%s" % (
374
 
            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,
375
296
            parent_bytes, noeol, self._raw_record)
376
297
        self._network_bytes = network_bytes
377
298
 
380
301
            if self._network_bytes is None:
381
302
                self._create_network_bytes()
382
303
            return self._network_bytes
383
 
        if ('-ft-' in self.storage_kind
384
 
                and storage_kind in ('chunked', 'fulltext')):
 
304
        if ('-ft-' in self.storage_kind and
 
305
            storage_kind in ('chunked', 'fulltext')):
385
306
            adapter_key = (self.storage_kind, 'fulltext')
386
307
            adapter_factory = adapter_registry.get(adapter_key)
387
308
            adapter = adapter_factory(None)
398
319
            elif storage_kind == 'fulltext':
399
320
                return self._knit.get_text(self.key[0])
400
321
        raise errors.UnavailableRepresentation(self.key, storage_kind,
401
 
                                               self.storage_kind)
 
322
            self.storage_kind)
402
323
 
403
324
 
404
325
class LazyKnitContentFactory(ContentFactory):
434
355
            else:
435
356
                # all the keys etc are contained in the bytes returned in the
436
357
                # first record.
437
 
                return b''
 
358
                return ''
438
359
        if storage_kind in ('chunked', 'fulltext'):
439
360
            chunks = self._generator._get_one_work(self.key).text()
440
361
            if storage_kind == 'chunked':
441
362
                return chunks
442
363
            else:
443
 
                return b''.join(chunks)
 
364
                return ''.join(chunks)
444
365
        raise errors.UnavailableRepresentation(self.key, storage_kind,
445
 
                                               self.storage_kind)
 
366
            self.storage_kind)
446
367
 
447
368
 
448
369
def knit_delta_closure_to_records(storage_kind, bytes, line_end):
463
384
    :param bytes: The bytes of the record on the network.
464
385
    """
465
386
    start = line_end
466
 
    line_end = bytes.find(b'\n', start)
467
 
    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'))
468
389
    start = line_end + 1
469
 
    line_end = bytes.find(b'\n', start)
 
390
    line_end = bytes.find('\n', start)
470
391
    parent_line = bytes[start:line_end]
471
 
    if parent_line == b'None:':
 
392
    if parent_line == 'None:':
472
393
        parents = None
473
394
    else:
474
395
        parents = tuple(
475
 
            [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')
476
397
             if segment])
477
398
    start = line_end + 1
478
 
    noeol = bytes[start:start + 1] == b'N'
 
399
    noeol = bytes[start] == 'N'
479
400
    if 'ft' in storage_kind:
480
401
        method = 'fulltext'
481
402
    else:
485
406
    raw_record = bytes[start:]
486
407
    annotated = 'annotated' in storage_kind
487
408
    return [KnitContentFactory(key, parents, build_details, None, raw_record,
488
 
                               annotated, network_bytes=bytes)]
 
409
        annotated, network_bytes=bytes)]
489
410
 
490
411
 
491
412
class KnitContent(object):
492
413
    """Content of a knit version to which deltas can be applied.
493
414
 
494
 
    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,
495
416
    plus a flag saying if the final ending is really there or not, because that
496
417
    corresponds to the on-disk knit representation.
497
418
    """
529
450
            if n > 0:
530
451
                # knit deltas do not provide reliable info about whether the
531
452
                # last line of a file matches, due to eol handling.
532
 
                if source[s_pos + n - 1] != target[t_pos + n - 1]:
533
 
                    n -= 1
 
453
                if source[s_pos + n -1] != target[t_pos + n -1]:
 
454
                    n-=1
534
455
                if n > 0:
535
456
                    yield s_pos, t_pos, n
536
457
            t_pos += t_len + true_n
537
458
            s_pos = s_end
538
459
        n = target_len - t_pos
539
460
        if n > 0:
540
 
            if source[s_pos + n - 1] != target[t_pos + n - 1]:
541
 
                n -= 1
 
461
            if source[s_pos + n -1] != target[t_pos + n -1]:
 
462
                n-=1
542
463
            if n > 0:
543
464
                yield s_pos, t_pos, n
544
465
        yield s_pos + (target_len - t_pos), target_len, 0
549
470
 
550
471
    def __init__(self, lines):
551
472
        KnitContent.__init__(self)
552
 
        self._lines = list(lines)
 
473
        self._lines = lines
553
474
 
554
475
    def annotate(self):
555
476
        """Return a list of (origin, text) for each content line."""
556
477
        lines = self._lines[:]
557
478
        if self._should_strip_eol:
558
479
            origin, last_line = lines[-1]
559
 
            lines[-1] = (origin, last_line.rstrip(b'\n'))
 
480
            lines[-1] = (origin, last_line.rstrip('\n'))
560
481
        return lines
561
482
 
562
483
    def apply_delta(self, delta, new_version_id):
564
485
        offset = 0
565
486
        lines = self._lines
566
487
        for start, end, count, delta_lines in delta:
567
 
            lines[offset + start:offset + end] = delta_lines
 
488
            lines[offset+start:offset+end] = delta_lines
568
489
            offset = offset + (start - end) + count
569
490
 
570
491
    def text(self):
571
492
        try:
572
493
            lines = [text for origin, text in self._lines]
573
 
        except ValueError as e:
 
494
        except ValueError, e:
574
495
            # most commonly (only?) caused by the internal form of the knit
575
496
            # missing annotation information because of a bug - see thread
576
497
            # around 20071015
577
498
            raise KnitCorrupt(self,
578
 
                              "line in annotated knit missing annotation information: %s"
579
 
                              % (e,))
 
499
                "line in annotated knit missing annotation information: %s"
 
500
                % (e,))
580
501
        if self._should_strip_eol:
581
 
            lines[-1] = lines[-1].rstrip(b'\n')
 
502
            lines[-1] = lines[-1].rstrip('\n')
582
503
        return lines
583
504
 
584
505
    def copy(self):
585
 
        return AnnotatedKnitContent(self._lines)
 
506
        return AnnotatedKnitContent(self._lines[:])
586
507
 
587
508
 
588
509
class PlainKnitContent(KnitContent):
607
528
        offset = 0
608
529
        lines = self._lines
609
530
        for start, end, count, delta_lines in delta:
610
 
            lines[offset + start:offset + end] = delta_lines
 
531
            lines[offset+start:offset+end] = delta_lines
611
532
            offset = offset + (start - end) + count
612
533
        self._version_id = new_version_id
613
534
 
618
539
        lines = self._lines
619
540
        if self._should_strip_eol:
620
541
            lines = lines[:]
621
 
            lines[-1] = lines[-1].rstrip(b'\n')
 
542
            lines[-1] = lines[-1].rstrip('\n')
622
543
        return lines
623
544
 
624
545
 
677
598
        #       but the code itself doesn't really depend on that.
678
599
        #       Figure out a way to not require the overhead of turning the
679
600
        #       list back into tuples.
680
 
        lines = (tuple(line.split(b' ', 1)) for line in content)
 
601
        lines = [tuple(line.split(' ', 1)) for line in content]
681
602
        return AnnotatedKnitContent(lines)
682
603
 
683
604
    def parse_line_delta_iter(self, lines):
699
620
        """
700
621
        result = []
701
622
        lines = iter(lines)
 
623
        next = lines.next
702
624
 
703
625
        cache = {}
704
 
 
705
626
        def cache_and_return(line):
706
 
            origin, text = line.split(b' ', 1)
 
627
            origin, text = line.split(' ', 1)
707
628
            return cache.setdefault(origin, origin), text
708
629
 
709
630
        # walk through the lines parsing.
711
632
        # loop to minimise any performance impact
712
633
        if plain:
713
634
            for header in lines:
714
 
                start, end, count = [int(n) for n in header.split(b',')]
715
 
                contents = [next(lines).split(b' ', 1)[1]
716
 
                            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)]
717
637
                result.append((start, end, count, contents))
718
638
        else:
719
639
            for header in lines:
720
 
                start, end, count = [int(n) for n in header.split(b',')]
721
 
                contents = [tuple(next(lines).split(b' ', 1))
722
 
                            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)]
723
642
                result.append((start, end, count, contents))
724
643
        return result
725
644
 
726
645
    def get_fulltext_content(self, lines):
727
646
        """Extract just the content lines from a fulltext."""
728
 
        return (line.split(b' ', 1)[1] for line in lines)
 
647
        return (line.split(' ', 1)[1] for line in lines)
729
648
 
730
649
    def get_linedelta_content(self, lines):
731
650
        """Extract just the content from a line delta.
734
653
        Only the actual content lines.
735
654
        """
736
655
        lines = iter(lines)
 
656
        next = lines.next
737
657
        for header in lines:
738
 
            header = header.split(b',')
 
658
            header = header.split(',')
739
659
            count = int(header[2])
740
 
            for _ in range(count):
741
 
                origin, text = next(lines).split(b' ', 1)
 
660
            for i in xrange(count):
 
661
                origin, text = next().split(' ', 1)
742
662
                yield text
743
663
 
744
664
    def lower_fulltext(self, content):
746
666
 
747
667
        see parse_fulltext which this inverts.
748
668
        """
749
 
        return [b'%s %s' % (o, t) for o, t in content._lines]
 
669
        return ['%s %s' % (o, t) for o, t in content._lines]
750
670
 
751
671
    def lower_line_delta(self, delta):
752
672
        """convert a delta into a serializable form.
757
677
        #       the origin is a valid utf-8 line, eventually we could remove it
758
678
        out = []
759
679
        for start, end, c, lines in delta:
760
 
            out.append(b'%d,%d,%d\n' % (start, end, c))
761
 
            out.extend(origin + b' ' + text
 
680
            out.append('%d,%d,%d\n' % (start, end, c))
 
681
            out.extend(origin + ' ' + text
762
682
                       for origin, text in lines)
763
683
        return out
764
684
 
766
686
        content = knit._get_content(key)
767
687
        # adjust for the fact that serialised annotations are only key suffixes
768
688
        # for this factory.
769
 
        if isinstance(key, tuple):
 
689
        if type(key) is tuple:
770
690
            prefix = key[:-1]
771
691
            origins = content.annotate()
772
692
            result = []
801
721
        while cur < num_lines:
802
722
            header = lines[cur]
803
723
            cur += 1
804
 
            start, end, c = [int(n) for n in header.split(b',')]
805
 
            yield start, end, c, lines[cur:cur + c]
 
724
            start, end, c = [int(n) for n in header.split(',')]
 
725
            yield start, end, c, lines[cur:cur+c]
806
726
            cur += c
807
727
 
808
728
    def parse_line_delta(self, lines, version_id):
819
739
        Only the actual content lines.
820
740
        """
821
741
        lines = iter(lines)
 
742
        next = lines.next
822
743
        for header in lines:
823
 
            header = header.split(b',')
 
744
            header = header.split(',')
824
745
            count = int(header[2])
825
 
            for _ in range(count):
826
 
                yield next(lines)
 
746
            for i in xrange(count):
 
747
                yield next()
827
748
 
828
749
    def lower_fulltext(self, content):
829
750
        return content.text()
831
752
    def lower_line_delta(self, delta):
832
753
        out = []
833
754
        for start, end, c, lines in delta:
834
 
            out.append(b'%d,%d,%d\n' % (start, end, c))
 
755
            out.append('%d,%d,%d\n' % (start, end, c))
835
756
            out.extend(lines)
836
757
        return out
837
758
 
840
761
        return annotator.annotate_flat(key)
841
762
 
842
763
 
 
764
 
843
765
def make_file_factory(annotated, mapper):
844
766
    """Create a factory for creating a file based KnitVersionedFiles.
845
767
 
850
772
    :param mapper: The mapper from keys to paths.
851
773
    """
852
774
    def factory(transport):
853
 
        index = _KndxIndex(transport, mapper, lambda: None,
854
 
                           lambda: True, lambda: True)
 
775
        index = _KndxIndex(transport, mapper, lambda:None, lambda:True, lambda:True)
855
776
        access = _KnitKeyAccess(transport, mapper)
856
777
        return KnitVersionedFiles(index, access, annotated=annotated)
857
778
    return factory
878
799
        else:
879
800
            max_delta_chain = 0
880
801
        graph_index = _mod_index.InMemoryGraphIndex(reference_lists=ref_length,
881
 
                                                    key_elements=keylength)
 
802
            key_elements=keylength)
882
803
        stream = transport.open_write_stream('newpack')
883
804
        writer = pack.ContainerWriter(stream.write)
884
805
        writer.begin()
885
 
        index = _KnitGraphIndex(graph_index, lambda: True, parents=parents,
886
 
                                deltas=delta, add_callback=graph_index.add_nodes)
887
 
        access = pack_repo._DirectPackAccess({})
 
806
        index = _KnitGraphIndex(graph_index, lambda:True, parents=parents,
 
807
            deltas=delta, add_callback=graph_index.add_nodes)
 
808
        access = _DirectPackAccess({})
888
809
        access.set_writer(writer, graph_index, (transport, 'newpack'))
889
810
        result = KnitVersionedFiles(index, access,
890
 
                                    max_delta_chain=max_delta_chain)
 
811
            max_delta_chain=max_delta_chain)
891
812
        result.stream = stream
892
813
        result.writer = writer
893
814
        return result
924
845
            if compression_parent not in all_build_index_memos:
925
846
                next_keys.add(compression_parent)
926
847
        build_keys = next_keys
927
 
    return sum(index_memo[2]
928
 
               for index_memo in viewvalues(all_build_index_memos))
929
 
 
930
 
 
931
 
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):
932
853
    """Storage for many versioned files using knit compression.
933
854
 
934
855
    Backend storage is managed by indices and data objects.
952
873
            stored during insertion.
953
874
        :param reload_func: An function that can be called if we think we need
954
875
            to reload the pack listing and try again. See
955
 
            'breezy.bzr.pack_repo.AggregateIndex' for the signature.
 
876
            'bzrlib.repofmt.pack_repo.AggregateIndex' for the signature.
956
877
        """
957
878
        self._index = index
958
879
        self._access = data_access
961
882
            self._factory = KnitAnnotateFactory()
962
883
        else:
963
884
            self._factory = KnitPlainFactory()
964
 
        self._immediate_fallback_vfs = []
 
885
        self._fallback_vfs = []
965
886
        self._reload_func = reload_func
966
887
 
967
888
    def __repr__(self):
970
891
            self._index,
971
892
            self._access)
972
893
 
973
 
    def without_fallbacks(self):
974
 
        """Return a clone of this object without any fallbacks configured."""
975
 
        return KnitVersionedFiles(self._index, self._access,
976
 
                                  self._max_delta_chain, self._factory.annotated,
977
 
                                  self._reload_func)
978
 
 
979
894
    def add_fallback_versioned_files(self, a_versioned_files):
980
895
        """Add a source of texts for texts not present in this knit.
981
896
 
982
897
        :param a_versioned_files: A VersionedFiles object.
983
898
        """
984
 
        self._immediate_fallback_vfs.append(a_versioned_files)
 
899
        self._fallback_vfs.append(a_versioned_files)
985
900
 
986
901
    def add_lines(self, key, parents, lines, parent_texts=None,
987
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
988
 
                  check_content=True):
 
902
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
903
        check_content=True):
989
904
        """See VersionedFiles.add_lines()."""
990
905
        self._index._check_write_ok()
991
906
        self._check_add(key, lines, random_id, check_content)
994
909
            # indexes can't directly store that, so we give them
995
910
            # an empty tuple instead.
996
911
            parents = ()
997
 
        line_bytes = b''.join(lines)
 
912
        line_bytes = ''.join(lines)
998
913
        return self._add(key, lines, parents,
999
 
                         parent_texts, left_matching_blocks, nostore_sha, random_id,
1000
 
                         line_bytes=line_bytes)
 
914
            parent_texts, left_matching_blocks, nostore_sha, random_id,
 
915
            line_bytes=line_bytes)
 
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)
1001
931
 
1002
932
    def _add(self, key, lines, parents, parent_texts,
1003
 
             left_matching_blocks, nostore_sha, random_id,
1004
 
             line_bytes):
 
933
        left_matching_blocks, nostore_sha, random_id,
 
934
        line_bytes):
1005
935
        """Add a set of lines on top of version specified by parents.
1006
936
 
1007
937
        Any versions not present will be converted into ghosts.
1033
963
                present_parents.append(parent)
1034
964
 
1035
965
        # Currently we can only compress against the left most present parent.
1036
 
        if (len(present_parents) == 0
1037
 
                or present_parents[0] != parents[0]):
 
966
        if (len(present_parents) == 0 or
 
967
            present_parents[0] != parents[0]):
1038
968
            delta = False
1039
969
        else:
1040
970
            # To speed the extract of texts the delta chain is limited
1048
978
        # Note: line_bytes is not modified to add a newline, that is tracked
1049
979
        #       via the no_eol flag. 'lines' *is* modified, because that is the
1050
980
        #       general values needed by the Content code.
1051
 
        if line_bytes and not line_bytes.endswith(b'\n'):
1052
 
            options.append(b'no-eol')
 
981
        if line_bytes and line_bytes[-1] != '\n':
 
982
            options.append('no-eol')
1053
983
            no_eol = True
1054
984
            # Copy the existing list, or create a new one
1055
985
            if lines is None:
1057
987
            else:
1058
988
                lines = lines[:]
1059
989
            # Replace the last line with one that ends in a final newline
1060
 
            lines[-1] = lines[-1] + b'\n'
 
990
            lines[-1] = lines[-1] + '\n'
1061
991
        if lines is None:
1062
992
            lines = osutils.split_lines(line_bytes)
1063
993
 
1064
994
        for element in key[:-1]:
1065
 
            if not isinstance(element, bytes):
1066
 
                raise TypeError("key contains non-bytestrings: %r" % (key,))
 
995
            if type(element) is not str:
 
996
                raise TypeError("key contains non-strings: %r" % (key,))
1067
997
        if key[-1] is None:
1068
 
            key = key[:-1] + (b'sha1:' + digest,)
1069
 
        elif not isinstance(key[-1], bytes):
1070
 
            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,))
1071
1001
        # Knit hunks are still last-element only
1072
1002
        version_id = key[-1]
1073
1003
        content = self._factory.make(lines, version_id)
1078
1008
        if delta or (self._factory.annotated and len(present_parents) > 0):
1079
1009
            # Merge annotations from parent texts if needed.
1080
1010
            delta_hunks = self._merge_annotations(content, present_parents,
1081
 
                                                  parent_texts, delta, self._factory.annotated,
1082
 
                                                  left_matching_blocks)
 
1011
                parent_texts, delta, self._factory.annotated,
 
1012
                left_matching_blocks)
1083
1013
 
1084
1014
        if delta:
1085
 
            options.append(b'line-delta')
 
1015
            options.append('line-delta')
1086
1016
            store_lines = self._factory.lower_line_delta(delta_hunks)
1087
 
            size, data = self._record_to_data(key, digest,
1088
 
                                              store_lines)
 
1017
            size, bytes = self._record_to_data(key, digest,
 
1018
                store_lines)
1089
1019
        else:
1090
 
            options.append(b'fulltext')
 
1020
            options.append('fulltext')
1091
1021
            # isinstance is slower and we have no hierarchy.
1092
1022
            if self._factory.__class__ is KnitPlainFactory:
1093
1023
                # Use the already joined bytes saving iteration time in
1094
1024
                # _record_to_data.
1095
1025
                dense_lines = [line_bytes]
1096
1026
                if no_eol:
1097
 
                    dense_lines.append(b'\n')
1098
 
                size, data = self._record_to_data(key, digest,
1099
 
                                                  lines, dense_lines)
 
1027
                    dense_lines.append('\n')
 
1028
                size, bytes = self._record_to_data(key, digest,
 
1029
                    lines, dense_lines)
1100
1030
            else:
1101
1031
                # get mixed annotation + content and feed it into the
1102
1032
                # serialiser.
1103
1033
                store_lines = self._factory.lower_fulltext(content)
1104
 
                size, data = self._record_to_data(key, digest,
1105
 
                                                  store_lines)
 
1034
                size, bytes = self._record_to_data(key, digest,
 
1035
                    store_lines)
1106
1036
 
1107
 
        access_memo = self._access.add_raw_records([(key, size)], data)[0]
 
1037
        access_memo = self._access.add_raw_records([(key, size)], bytes)[0]
1108
1038
        self._index.add_records(
1109
1039
            ((key, options, access_memo, parents),),
1110
1040
            random_id=random_id)
1136
1066
            if self._index.get_method(key) != 'fulltext':
1137
1067
                compression_parent = parent_map[key][0]
1138
1068
                if compression_parent not in parent_map:
1139
 
                    raise KnitCorrupt(self,
1140
 
                                      "Missing basis parent %s for %s" % (
1141
 
                                          compression_parent, key))
1142
 
        for fallback_vfs in self._immediate_fallback_vfs:
 
1069
                    raise errors.KnitCorrupt(self,
 
1070
                        "Missing basis parent %s for %s" % (
 
1071
                        compression_parent, key))
 
1072
        for fallback_vfs in self._fallback_vfs:
1143
1073
            fallback_vfs.check()
1144
1074
 
1145
1075
    def _check_add(self, key, lines, random_id, check_content):
1146
1076
        """check that version_id and lines are safe to add."""
1147
 
        if not all(isinstance(x, bytes) or x is None for x in key):
1148
 
            raise TypeError(key)
1149
1077
        version_id = key[-1]
1150
1078
        if version_id is not None:
1151
1079
            if contains_whitespace(version_id):
1171
1099
        """
1172
1100
        if rec[1] != version_id:
1173
1101
            raise KnitCorrupt(self,
1174
 
                              'unexpected version, wanted %r, got %r' % (version_id, rec[1]))
 
1102
                'unexpected version, wanted %r, got %r' % (version_id, rec[1]))
1175
1103
 
1176
1104
    def _check_should_delta(self, parent):
1177
1105
        """Iterate back through the parent listing, looking for a fulltext.
1186
1114
        """
1187
1115
        delta_size = 0
1188
1116
        fulltext_size = None
1189
 
        for count in range(self._max_delta_chain):
 
1117
        for count in xrange(self._max_delta_chain):
1190
1118
            try:
1191
1119
                # Note that this only looks in the index of this particular
1192
1120
                # KnitVersionedFiles, not in the fallbacks.  This ensures that
1194
1122
                # boundaries.
1195
1123
                build_details = self._index.get_build_details([parent])
1196
1124
                parent_details = build_details[parent]
1197
 
            except (RevisionNotPresent, KeyError) as e:
 
1125
            except (RevisionNotPresent, KeyError), e:
1198
1126
                # Some basis is not locally present: always fulltext
1199
1127
                return False
1200
1128
            index_memo, compression_parent, _, _ = parent_details
1225
1153
 
1226
1154
        A dict of key to (record_details, index_memo, next, parents) is
1227
1155
        returned.
1228
 
 
1229
 
        * method is the way referenced data should be applied.
1230
 
        * index_memo is the handle to pass to the data access to actually get
1231
 
          the data
1232
 
        * next is the build-parent of the version, or None for fulltexts.
1233
 
        * parents is the version_ids of the parents of this version
1234
 
 
1235
 
        :param allow_missing: If True do not raise an error on a missing
1236
 
            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.
1237
1164
        """
1238
1165
        component_data = {}
1239
1166
        pending_components = keys
1241
1168
            build_details = self._index.get_build_details(pending_components)
1242
1169
            current_components = set(pending_components)
1243
1170
            pending_components = set()
1244
 
            for key, details in viewitems(build_details):
 
1171
            for key, details in build_details.iteritems():
1245
1172
                (index_memo, compression_parent, parents,
1246
1173
                 record_details) = details
 
1174
                method = record_details[0]
1247
1175
                if compression_parent is not None:
1248
1176
                    pending_components.add(compression_parent)
1249
 
                component_data[key] = self._build_details_to_components(
1250
 
                    details)
 
1177
                component_data[key] = self._build_details_to_components(details)
1251
1178
            missing = current_components.difference(build_details)
1252
1179
            if missing and not allow_missing:
1253
1180
                raise errors.RevisionNotPresent(missing.pop(), self)
1265
1192
        generator = _VFContentMapGenerator(self, [key])
1266
1193
        return generator._get_content(key)
1267
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
 
1268
1208
    def get_parent_map(self, keys):
1269
1209
        """Get a map of the graph parents of keys.
1270
1210
 
1285
1225
            and so on.
1286
1226
        """
1287
1227
        result = {}
1288
 
        sources = [self._index] + self._immediate_fallback_vfs
 
1228
        sources = [self._index] + self._fallback_vfs
1289
1229
        source_results = []
1290
1230
        missing = set(keys)
1291
1231
        for source in sources:
1301
1241
        """Produce a dictionary of knit records.
1302
1242
 
1303
1243
        :return: {key:(record, record_details, digest, next)}
1304
 
 
1305
 
            * record: data returned from read_records (a KnitContentobject)
1306
 
            * record_details: opaque information to pass to parse_record
1307
 
            * digest: SHA1 digest of the full text after all steps are done
1308
 
            * 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.
1309
1252
                Will be None if the record is not a delta.
1310
 
 
1311
1253
        :param keys: The keys to build a map for
1312
1254
        :param allow_missing: If some records are missing, rather than
1313
1255
            error, just return the data that could be generated.
1314
1256
        """
1315
1257
        raw_map = self._get_record_map_unparsed(keys,
1316
 
                                                allow_missing=allow_missing)
 
1258
            allow_missing=allow_missing)
1317
1259
        return self._raw_map_to_record_map(raw_map)
1318
1260
 
1319
1261
    def _raw_map_to_record_map(self, raw_map):
1344
1286
        while True:
1345
1287
            try:
1346
1288
                position_map = self._get_components_positions(keys,
1347
 
                                                              allow_missing=allow_missing)
 
1289
                    allow_missing=allow_missing)
1348
1290
                # key = component_id, r = record_details, i_m = index_memo,
1349
1291
                # n = next
1350
1292
                records = [(key, i_m) for key, (r, i_m, n)
1351
 
                           in viewitems(position_map)]
 
1293
                                       in position_map.iteritems()]
1352
1294
                # Sort by the index memo, so that we request records from the
1353
1295
                # same pack file together, and in forward-sorted order
1354
1296
                records.sort(key=operator.itemgetter(1))
1357
1299
                    (record_details, index_memo, next) = position_map[key]
1358
1300
                    raw_record_map[key] = data, record_details, next
1359
1301
                return raw_record_map
1360
 
            except errors.RetryWithNewPacks as e:
 
1302
            except errors.RetryWithNewPacks, e:
1361
1303
                self._access.reload_or_raise(e)
1362
1304
 
1363
1305
    @classmethod
1383
1325
        prefix_order = []
1384
1326
        for key in keys:
1385
1327
            if len(key) == 1:
1386
 
                prefix = b''
 
1328
                prefix = ''
1387
1329
            else:
1388
1330
                prefix = key[0]
1389
1331
 
1462
1404
            try:
1463
1405
                keys = set(remaining_keys)
1464
1406
                for content_factory in self._get_remaining_record_stream(keys,
1465
 
                                                                         ordering, include_delta_closure):
 
1407
                                            ordering, include_delta_closure):
1466
1408
                    remaining_keys.discard(content_factory.key)
1467
1409
                    yield content_factory
1468
1410
                return
1469
 
            except errors.RetryWithNewPacks as e:
 
1411
            except errors.RetryWithNewPacks, e:
1470
1412
                self._access.reload_or_raise(e)
1471
1413
 
1472
1414
    def _get_remaining_record_stream(self, keys, ordering,
1473
1415
                                     include_delta_closure):
1474
1416
        """This function is the 'retry' portion for get_record_stream."""
1475
1417
        if include_delta_closure:
1476
 
            positions = self._get_components_positions(
1477
 
                keys, allow_missing=True)
 
1418
            positions = self._get_components_positions(keys, allow_missing=True)
1478
1419
        else:
1479
1420
            build_details = self._index.get_build_details(keys)
1480
1421
            # map from key to
1481
1422
            # (record_details, access_memo, compression_parent_key)
1482
1423
            positions = dict((key, self._build_details_to_components(details))
1483
 
                             for key, details in viewitems(build_details))
 
1424
                for key, details in build_details.iteritems())
1484
1425
        absent_keys = keys.difference(set(positions))
1485
1426
        # There may be more absent keys : if we're missing the basis component
1486
1427
        # and are trying to include the delta closure.
1538
1479
        else:
1539
1480
            if ordering != 'unordered':
1540
1481
                raise AssertionError('valid values for ordering are:'
1541
 
                                     ' "unordered", "groupcompress" or "topological" not: %r'
1542
 
                                     % (ordering,))
 
1482
                    ' "unordered", "groupcompress" or "topological" not: %r'
 
1483
                    % (ordering,))
1543
1484
            # Just group by source; remote sources first.
1544
1485
            present_keys = []
1545
1486
            source_keys = []
1582
1523
                    for key, raw_data in self._read_records_iter_unchecked(records):
1583
1524
                        (record_details, index_memo, _) = positions[key]
1584
1525
                        yield KnitContentFactory(key, global_map[key],
1585
 
                                                 record_details, None, raw_data, self._factory.annotated, None)
 
1526
                            record_details, None, raw_data, self._factory.annotated, None)
1586
1527
                else:
1587
 
                    vf = self._immediate_fallback_vfs[parent_maps.index(
1588
 
                        source) - 1]
 
1528
                    vf = self._fallback_vfs[parent_maps.index(source) - 1]
1589
1529
                    for record in vf.get_record_stream(keys, ordering,
1590
 
                                                       include_delta_closure):
 
1530
                        include_delta_closure):
1591
1531
                        yield record
1592
1532
 
1593
1533
    def get_sha1s(self, keys):
1595
1535
        missing = set(keys)
1596
1536
        record_map = self._get_record_map(missing, allow_missing=True)
1597
1537
        result = {}
1598
 
        for key, details in viewitems(record_map):
 
1538
        for key, details in record_map.iteritems():
1599
1539
            if key not in missing:
1600
1540
                continue
1601
1541
            # record entry 2 is the 'digest'.
1602
1542
            result[key] = details[2]
1603
1543
        missing.difference_update(set(result))
1604
 
        for source in self._immediate_fallback_vfs:
 
1544
        for source in self._fallback_vfs:
1605
1545
            if not missing:
1606
1546
                break
1607
1547
            new_result = source.get_sha1s(missing)
1632
1572
        else:
1633
1573
            # self is not annotated, but we can strip annotations cheaply.
1634
1574
            annotated = ""
1635
 
            convertibles = {"knit-annotated-ft-gz"}
 
1575
            convertibles = set(["knit-annotated-ft-gz"])
1636
1576
            if self._max_delta_chain:
1637
1577
                delta_types.add("knit-annotated-delta-gz")
1638
1578
                convertibles.add("knit-annotated-delta-gz")
1676
1616
            # Raise an error when a record is missing.
1677
1617
            if record.storage_kind == 'absent':
1678
1618
                raise RevisionNotPresent([record.key], self)
1679
 
            elif ((record.storage_kind in knit_types) and
1680
 
                  (compression_parent is None or
1681
 
                   not self._immediate_fallback_vfs or
1682
 
                   compression_parent in self._index or
1683
 
                   compression_parent not in self)):
 
1619
            elif ((record.storage_kind in knit_types)
 
1620
                  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))):
1684
1624
                # we can insert the knit record literally if either it has no
1685
1625
                # compression parent OR we already have its basis in this kvf
1686
1626
                # OR the basis is not present even in the fallbacks.  In the
1688
1628
                # will be well, or it won't turn up at all and we'll raise an
1689
1629
                # error at the end.
1690
1630
                #
1691
 
                # TODO: self.__contains__ is somewhat redundant with
1692
 
                # 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
1693
1633
                # asks if it's only present in the fallbacks. -- mbp 20081119
1694
1634
                if record.storage_kind not in native_types:
1695
1635
                    try:
1703
1643
                    # It's a knit record, it has a _raw_record field (even if
1704
1644
                    # it was reconstituted from a network stream).
1705
1645
                    bytes = record._raw_record
1706
 
                options = [record._build_details[0].encode('ascii')]
 
1646
                options = [record._build_details[0]]
1707
1647
                if record._build_details[1]:
1708
 
                    options.append(b'no-eol')
 
1648
                    options.append('no-eol')
1709
1649
                # Just blat it across.
1710
1650
                # Note: This does end up adding data on duplicate keys. As
1711
1651
                # modern repositories use atomic insertions this should not
1717
1657
                access_memo = self._access.add_raw_records(
1718
1658
                    [(record.key, len(bytes))], bytes)[0]
1719
1659
                index_entry = (record.key, options, access_memo, parents)
1720
 
                if b'fulltext' not in options:
 
1660
                if 'fulltext' not in options:
1721
1661
                    # Not a fulltext, so we need to make sure the compression
1722
1662
                    # parent will also be present.
1723
1663
                    # Note that pack backed knits don't need to buffer here
1728
1668
                    #
1729
1669
                    # They're required to be physically in this
1730
1670
                    # KnitVersionedFiles, not in a fallback.
1731
 
                    if compression_parent not in self._index:
 
1671
                    if not self._index.has_key(compression_parent):
1732
1672
                        pending = buffered_index_entries.setdefault(
1733
1673
                            compression_parent, [])
1734
1674
                        pending.append(index_entry)
1737
1677
                    self._index.add_records([index_entry])
1738
1678
            elif record.storage_kind == 'chunked':
1739
1679
                self.add_lines(record.key, parents,
1740
 
                               osutils.chunks_to_lines(record.get_bytes_as('chunked')))
 
1680
                    osutils.chunks_to_lines(record.get_bytes_as('chunked')))
1741
1681
            else:
1742
1682
                # Not suitable for direct insertion as a
1743
1683
                # delta, either because it's not the right format, or this
1827
1767
                # we need key, position, length
1828
1768
                key_records = []
1829
1769
                build_details = self._index.get_build_details(keys)
1830
 
                for key, details in viewitems(build_details):
 
1770
                for key, details in build_details.iteritems():
1831
1771
                    if key in keys:
1832
1772
                        key_records.append((key, details[0]))
1833
1773
                records_iter = enumerate(self._read_records_iter(key_records))
1834
1774
                for (key_idx, (key, data, sha_value)) in records_iter:
1835
 
                    pb.update(gettext('Walking content'), key_idx, total)
 
1775
                    pb.update('Walking content', key_idx, total)
1836
1776
                    compression_parent = build_details[key][1]
1837
1777
                    if compression_parent is None:
1838
1778
                        # fulltext
1839
 
                        line_iterator = self._factory.get_fulltext_content(
1840
 
                            data)
 
1779
                        line_iterator = self._factory.get_fulltext_content(data)
1841
1780
                    else:
1842
1781
                        # Delta
1843
 
                        line_iterator = self._factory.get_linedelta_content(
1844
 
                            data)
 
1782
                        line_iterator = self._factory.get_linedelta_content(data)
1845
1783
                    # Now that we are yielding the data for this key, remove it
1846
1784
                    # from the list
1847
1785
                    keys.remove(key)
1852
1790
                    for line in line_iterator:
1853
1791
                        yield line, key
1854
1792
                done = True
1855
 
            except errors.RetryWithNewPacks as e:
 
1793
            except errors.RetryWithNewPacks, e:
1856
1794
                self._access.reload_or_raise(e)
1857
1795
        # If there are still keys we've not yet found, we look in the fallback
1858
1796
        # vfs, and hope to find them there.  Note that if the keys are found
1859
1797
        # but had no changes or no content, the fallback may not return
1860
1798
        # anything.
1861
 
        if keys and not self._immediate_fallback_vfs:
 
1799
        if keys and not self._fallback_vfs:
1862
1800
            # XXX: strictly the second parameter is meant to be the file id
1863
1801
            # but it's not easily accessible here.
1864
1802
            raise RevisionNotPresent(keys, repr(self))
1865
 
        for source in self._immediate_fallback_vfs:
 
1803
        for source in self._fallback_vfs:
1866
1804
            if not keys:
1867
1805
                break
1868
1806
            source_keys = set()
1870
1808
                source_keys.add(key)
1871
1809
                yield line, key
1872
1810
            keys.difference_update(source_keys)
1873
 
        pb.update(gettext('Walking content'), total, total)
 
1811
        pb.update('Walking content', total, total)
1874
1812
 
1875
1813
    def _make_line_delta(self, delta_seq, new_content):
1876
1814
        """Generate a line delta from delta_seq and new_content."""
1878
1816
        for op in delta_seq.get_opcodes():
1879
1817
            if op[0] == 'equal':
1880
1818
                continue
1881
 
            diff_hunks.append(
1882
 
                (op[1], op[2], op[4] - op[3], new_content._lines[op[3]:op[4]]))
 
1819
            diff_hunks.append((op[1], op[2], op[4]-op[3], new_content._lines[op[3]:op[4]]))
1883
1820
        return diff_hunks
1884
1821
 
1885
1822
    def _merge_annotations(self, content, parents, parent_texts={},
1909
1846
                    # this copies (origin, text) pairs across to the new
1910
1847
                    # content for any line that matches the last-checked
1911
1848
                    # parent.
1912
 
                    content._lines[j:j + n] = merge_content._lines[i:i + n]
 
1849
                    content._lines[j:j+n] = merge_content._lines[i:i+n]
1913
1850
            # XXX: Robert says the following block is a workaround for a
1914
1851
            # now-fixed bug and it can probably be deleted. -- mbp 20080618
1915
 
            if content._lines and not content._lines[-1][1].endswith(b'\n'):
 
1852
            if content._lines and content._lines[-1][1][-1] != '\n':
1916
1853
                # The copied annotation was from a line without a trailing EOL,
1917
1854
                # reinstate one for the content object, to ensure correct
1918
1855
                # serialization.
1919
 
                line = content._lines[-1][1] + b'\n'
 
1856
                line = content._lines[-1][1] + '\n'
1920
1857
                content._lines[-1] = (content._lines[-1][0], line)
1921
1858
        if delta:
1922
1859
            if delta_seq is None:
1924
1861
                new_texts = content.text()
1925
1862
                old_texts = reference_content.text()
1926
1863
                delta_seq = patiencediff.PatienceSequenceMatcher(
1927
 
                    None, old_texts, new_texts)
 
1864
                                                 None, old_texts, new_texts)
1928
1865
            return self._make_line_delta(delta_seq, content)
1929
1866
 
1930
1867
    def _parse_record(self, version_id, data):
1942
1879
        :return: the header and the decompressor stream.
1943
1880
                 as (stream, header_record)
1944
1881
        """
1945
 
        df = gzip.GzipFile(mode='rb', fileobj=BytesIO(raw_data))
 
1882
        df = tuned_gzip.GzipFile(mode='rb', fileobj=StringIO(raw_data))
1946
1883
        try:
1947
1884
            # Current serialise
1948
1885
            rec = self._check_header(key, df.readline())
1949
 
        except Exception as e:
 
1886
        except Exception, e:
1950
1887
            raise KnitCorrupt(self,
1951
1888
                              "While reading {%s} got %s(%s)"
1952
1889
                              % (key, e.__class__.__name__, str(e)))
1957
1894
        # 4168 calls in 2880 217 internal
1958
1895
        # 4168 calls to _parse_record_header in 2121
1959
1896
        # 4168 calls to readlines in 330
1960
 
        with gzip.GzipFile(mode='rb', fileobj=BytesIO(data)) as df:
1961
 
            try:
1962
 
                record_contents = df.readlines()
1963
 
            except Exception as e:
1964
 
                raise KnitCorrupt(self, "Corrupt compressed record %r, got %s(%s)" %
1965
 
                                  (data, e.__class__.__name__, str(e)))
1966
 
            header = record_contents.pop(0)
1967
 
            rec = self._split_header(header)
1968
 
            last_line = record_contents.pop()
1969
 
            if len(record_contents) != int(rec[2]):
1970
 
                raise KnitCorrupt(self,
1971
 
                                  'incorrect number of lines %s != %s'
1972
 
                                  ' for version {%s} %s'
1973
 
                                  % (len(record_contents), int(rec[2]),
1974
 
                                     rec[1], record_contents))
1975
 
            if last_line != b'end %s\n' % rec[1]:
1976
 
                raise KnitCorrupt(self,
1977
 
                                  'unexpected version end line %r, wanted %r'
1978
 
                                  % (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()
1979
1917
        return rec, record_contents
1980
1918
 
1981
1919
    def _read_records_iter(self, records):
1984
1922
        The result will be returned in whatever is the fastest to read.
1985
1923
        Not by the order requested. Also, multiple requests for the same
1986
1924
        record will only yield 1 response.
1987
 
 
1988
1925
        :param records: A list of (key, access_memo) entries
1989
1926
        :return: Yields (key, contents, digest) in the order
1990
1927
                 read, not the order requested
2002
1939
        raw_data = self._access.get_raw_records(
2003
1940
            [index_memo for key, index_memo in needed_records])
2004
1941
 
2005
 
        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):
2006
1944
            content, digest = self._parse_record(key[-1], data)
2007
1945
            yield key, content, digest
2008
1946
 
2034
1972
        if len(records):
2035
1973
            # grab the disk data needed.
2036
1974
            needed_offsets = [index_memo for key, index_memo
2037
 
                              in records]
 
1975
                                           in records]
2038
1976
            raw_records = self._access.get_raw_records(needed_offsets)
2039
1977
 
2040
1978
        for key, index_memo in records:
2041
 
            data = next(raw_records)
 
1979
            data = raw_records.next()
2042
1980
            yield key, data
2043
1981
 
2044
1982
    def _record_to_data(self, key, digest, lines, dense_lines=None):
2047
1985
        :param key: The key of the record. Currently keys are always serialised
2048
1986
            using just the trailing component.
2049
1987
        :param dense_lines: The bytes of lines but in a denser form. For
2050
 
            instance, if lines is a list of 1000 bytestrings each ending in
2051
 
            \\n, dense_lines may be a list with one line in it, containing all
2052
 
            the 1000's lines and their \\n's. Using dense_lines if it is
2053
 
            already known is a win because the string join to create bytes in
2054
 
            this function spends less time resizing the final string.
2055
 
        :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.)
2056
1994
        """
2057
 
        chunks = [b"version %s %d %s\n" % (key[-1], len(lines), digest)]
 
1995
        chunks = ["version %s %d %s\n" % (key[-1], len(lines), digest)]
2058
1996
        chunks.extend(dense_lines or lines)
2059
 
        chunks.append(b"end " + key[-1] + b"\n")
 
1997
        chunks.append("end %s\n" % key[-1])
2060
1998
        for chunk in chunks:
2061
 
            if not isinstance(chunk, bytes):
 
1999
            if type(chunk) is not str:
2062
2000
                raise AssertionError(
2063
2001
                    'data must be plain bytes was %s' % type(chunk))
2064
 
        if lines and not lines[-1].endswith(b'\n'):
 
2002
        if lines and lines[-1][-1] != '\n':
2065
2003
            raise ValueError('corrupt lines value %r' % lines)
2066
 
        compressed_bytes = b''.join(tuned_gzip.chunks_to_gzip(chunks))
 
2004
        compressed_bytes = tuned_gzip.chunks_to_gzip(chunks)
2067
2005
        return len(compressed_bytes), compressed_bytes
2068
2006
 
2069
2007
    def _split_header(self, line):
2077
2015
        """See VersionedFiles.keys."""
2078
2016
        if 'evil' in debug.debug_flags:
2079
2017
            trace.mutter_callsite(2, "keys scales with size of history")
2080
 
        sources = [self._index] + self._immediate_fallback_vfs
 
2018
        sources = [self._index] + self._fallback_vfs
2081
2019
        result = set()
2082
2020
        for source in sources:
2083
2021
            result.update(source.keys())
2095
2033
        # Note that _get_content is only called when the _ContentMapGenerator
2096
2034
        # has been constructed with just one key requested for reconstruction.
2097
2035
        if key in self.nonlocal_keys:
2098
 
            record = next(self.get_record_stream())
 
2036
            record = self.get_record_stream().next()
2099
2037
            # Create a content object on the fly
2100
2038
            lines = osutils.chunks_to_lines(record.get_bytes_as('chunked'))
2101
2039
            return PlainKnitContent(lines, record.key)
2123
2061
 
2124
2062
        missing_keys = set(nonlocal_keys)
2125
2063
        # Read from remote versioned file instances and provide to our caller.
2126
 
        for source in self.vf._immediate_fallback_vfs:
 
2064
        for source in self.vf._fallback_vfs:
2127
2065
            if not missing_keys:
2128
2066
                break
2129
2067
            # Loop over fallback repositories asking them for texts - ignore
2130
2068
            # any missing from a particular fallback.
2131
2069
            for record in source.get_record_stream(missing_keys,
2132
 
                                                   self._ordering, True):
 
2070
                self._ordering, True):
2133
2071
                if record.storage_kind == 'absent':
2134
2072
                    # Not in thie particular stream, may be in one of the
2135
2073
                    # other fallback vfs objects.
2189
2127
                    content = self._contents_map[component_id]
2190
2128
                else:
2191
2129
                    content, delta = self._factory.parse_record(key[-1],
2192
 
                                                                record, record_details, content,
2193
 
                                                                copy_base_content=multiple_versions)
 
2130
                        record, record_details, content,
 
2131
                        copy_base_content=multiple_versions)
2194
2132
                    if multiple_versions:
2195
2133
                        self._contents_map[component_id] = content
2196
2134
 
2218
2156
        """
2219
2157
        lines = []
2220
2158
        # kind marker for dispatch on the far side,
2221
 
        lines.append(b'knit-delta-closure')
 
2159
        lines.append('knit-delta-closure')
2222
2160
        # Annotated or not
2223
2161
        if self.vf._factory.annotated:
2224
 
            lines.append(b'annotated')
 
2162
            lines.append('annotated')
2225
2163
        else:
2226
 
            lines.append(b'')
 
2164
            lines.append('')
2227
2165
        # then the list of keys
2228
 
        lines.append(b'\t'.join(b'\x00'.join(key) for key in self.keys
2229
 
                                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]))
2230
2168
        # then the _raw_record_map in serialised form:
2231
2169
        map_byte_list = []
2232
2170
        # for each item in the map:
2237
2175
        # one line with next ('' for None)
2238
2176
        # one line with byte count of the record bytes
2239
2177
        # the record bytes
2240
 
        for key, (record_bytes, (method, noeol), next) in viewitems(
2241
 
                self._raw_record_map):
2242
 
            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)
2243
2181
            parents = self.global_map.get(key, None)
2244
2182
            if parents is None:
2245
 
                parent_bytes = b'None:'
 
2183
                parent_bytes = 'None:'
2246
2184
            else:
2247
 
                parent_bytes = b'\t'.join(b'\x00'.join(key) for key in parents)
2248
 
            method_bytes = method.encode('ascii')
 
2185
                parent_bytes = '\t'.join('\x00'.join(key) for key in parents)
 
2186
            method_bytes = method
2249
2187
            if noeol:
2250
 
                noeol_bytes = b"T"
 
2188
                noeol_bytes = "T"
2251
2189
            else:
2252
 
                noeol_bytes = b"F"
 
2190
                noeol_bytes = "F"
2253
2191
            if next:
2254
 
                next_bytes = b'\x00'.join(next)
 
2192
                next_bytes = '\x00'.join(next)
2255
2193
            else:
2256
 
                next_bytes = b''
2257
 
            map_byte_list.append(b'\n'.join(
2258
 
                [key_bytes, parent_bytes, method_bytes, noeol_bytes, next_bytes,
2259
 
                 b'%d' % len(record_bytes), record_bytes]))
2260
 
        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)
2261
2199
        lines.append(map_bytes)
2262
 
        bytes = b'\n'.join(lines)
 
2200
        bytes = '\n'.join(lines)
2263
2201
        return bytes
2264
2202
 
2265
2203
 
2267
2205
    """Content map generator reading from a VersionedFiles object."""
2268
2206
 
2269
2207
    def __init__(self, versioned_files, keys, nonlocal_keys=None,
2270
 
                 global_map=None, raw_record_map=None, ordering='unordered'):
 
2208
        global_map=None, raw_record_map=None, ordering='unordered'):
2271
2209
        """Create a _ContentMapGenerator.
2272
2210
 
2273
2211
        :param versioned_files: The versioned files that the texts are being
2302
2240
        self._record_map = None
2303
2241
        if raw_record_map is None:
2304
2242
            self._raw_record_map = self.vf._get_record_map_unparsed(keys,
2305
 
                                                                    allow_missing=True)
 
2243
                allow_missing=True)
2306
2244
        else:
2307
2245
            self._raw_record_map = raw_record_map
2308
2246
        # the factory for parsing records
2324
2262
        self.vf = KnitVersionedFiles(None, None)
2325
2263
        start = line_end
2326
2264
        # Annotated or not
2327
 
        line_end = bytes.find(b'\n', start)
 
2265
        line_end = bytes.find('\n', start)
2328
2266
        line = bytes[start:line_end]
2329
2267
        start = line_end + 1
2330
 
        if line == b'annotated':
 
2268
        if line == 'annotated':
2331
2269
            self._factory = KnitAnnotateFactory()
2332
2270
        else:
2333
2271
            self._factory = KnitPlainFactory()
2334
2272
        # list of keys to emit in get_record_stream
2335
 
        line_end = bytes.find(b'\n', start)
 
2273
        line_end = bytes.find('\n', start)
2336
2274
        line = bytes[start:line_end]
2337
2275
        start = line_end + 1
2338
2276
        self.keys = [
2339
 
            tuple(segment.split(b'\x00')) for segment in line.split(b'\t')
 
2277
            tuple(segment.split('\x00')) for segment in line.split('\t')
2340
2278
            if segment]
2341
2279
        # now a loop until the end. XXX: It would be nice if this was just a
2342
2280
        # bunch of the same records as get_record_stream(..., False) gives, but
2344
2282
        end = len(bytes)
2345
2283
        while start < end:
2346
2284
            # 1 line with key
2347
 
            line_end = bytes.find(b'\n', start)
2348
 
            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'))
2349
2287
            start = line_end + 1
2350
2288
            # 1 line with parents (None: for None, '' for ())
2351
 
            line_end = bytes.find(b'\n', start)
 
2289
            line_end = bytes.find('\n', start)
2352
2290
            line = bytes[start:line_end]
2353
 
            if line == b'None:':
 
2291
            if line == 'None:':
2354
2292
                parents = None
2355
2293
            else:
2356
2294
                parents = tuple(
2357
 
                    tuple(segment.split(b'\x00')) for segment in line.split(b'\t')
2358
 
                    if segment)
 
2295
                    [tuple(segment.split('\x00')) for segment in line.split('\t')
 
2296
                     if segment])
2359
2297
            self.global_map[key] = parents
2360
2298
            start = line_end + 1
2361
2299
            # one line with method
2362
 
            line_end = bytes.find(b'\n', start)
 
2300
            line_end = bytes.find('\n', start)
2363
2301
            line = bytes[start:line_end]
2364
 
            method = line.decode('ascii')
 
2302
            method = line
2365
2303
            start = line_end + 1
2366
2304
            # one line with noeol
2367
 
            line_end = bytes.find(b'\n', start)
 
2305
            line_end = bytes.find('\n', start)
2368
2306
            line = bytes[start:line_end]
2369
 
            noeol = line == b"T"
 
2307
            noeol = line == "T"
2370
2308
            start = line_end + 1
2371
 
            # one line with next (b'' for None)
2372
 
            line_end = bytes.find(b'\n', start)
 
2309
            # one line with next ('' for None)
 
2310
            line_end = bytes.find('\n', start)
2373
2311
            line = bytes[start:line_end]
2374
2312
            if not line:
2375
2313
                next = None
2376
2314
            else:
2377
 
                next = tuple(bytes[start:line_end].split(b'\x00'))
 
2315
                next = tuple(bytes[start:line_end].split('\x00'))
2378
2316
            start = line_end + 1
2379
2317
            # one line with byte count of the record bytes
2380
 
            line_end = bytes.find(b'\n', start)
 
2318
            line_end = bytes.find('\n', start)
2381
2319
            line = bytes[start:line_end]
2382
2320
            count = int(line)
2383
2321
            start = line_end + 1
2384
2322
            # the record bytes
2385
 
            record_bytes = bytes[start:start + count]
 
2323
            record_bytes = bytes[start:start+count]
2386
2324
            start = start + count
2387
2325
            # put it in the map
2388
2326
            self._raw_record_map[key] = (record_bytes, (method, noeol), next)
2457
2395
        ABI change with the C extension that reads .kndx files.
2458
2396
    """
2459
2397
 
2460
 
    HEADER = b"# bzr knit index 8\n"
 
2398
    HEADER = "# bzr knit index 8\n"
2461
2399
 
2462
2400
    def __init__(self, transport, mapper, get_scope, allow_writes, is_locked):
2463
2401
        """Create a _KndxIndex on transport using mapper."""
2502
2440
 
2503
2441
            try:
2504
2442
                for key, options, (_, pos, size), parents in path_keys:
2505
 
                    if not all(isinstance(option, bytes) for option in options):
2506
 
                        raise TypeError(options)
2507
2443
                    if parents is None:
2508
2444
                        # kndx indices cannot be parentless.
2509
2445
                        parents = ()
2510
 
                    line = b' '.join([
2511
 
                        b'\n'
2512
 
                        + key[-1], b','.join(options), b'%d' % pos, b'%d' % size,
2513
 
                        self._dictionary_compress(parents), b':'])
2514
 
                    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:
2515
2450
                        raise AssertionError(
2516
2451
                            'data must be utf8 was %s' % type(line))
2517
2452
                    lines.append(line)
2518
2453
                    self._cache_key(key, options, pos, size, parents)
2519
2454
                if len(orig_history):
2520
 
                    self._transport.append_bytes(path, b''.join(lines))
 
2455
                    self._transport.append_bytes(path, ''.join(lines))
2521
2456
                else:
2522
2457
                    self._init_index(path, lines)
2523
2458
            except:
2561
2496
        else:
2562
2497
            index = cache[version_id][5]
2563
2498
        cache[version_id] = (version_id,
2564
 
                             options,
2565
 
                             pos,
2566
 
                             size,
2567
 
                             parents,
2568
 
                             index)
 
2499
                                   options,
 
2500
                                   pos,
 
2501
                                   size,
 
2502
                                   parents,
 
2503
                                   index)
2569
2504
 
2570
2505
    def check_header(self, fp):
2571
2506
        line = fp.readline()
2572
 
        if line == b'':
 
2507
        if line == '':
2573
2508
            # An empty file can actually be treated as though the file doesn't
2574
2509
            # exist yet.
2575
2510
            raise errors.NoSuchFile(self)
2614
2549
        result = {}
2615
2550
        for key in keys:
2616
2551
            if key not in parent_map:
2617
 
                continue  # Ghost
 
2552
                continue # Ghost
2618
2553
            method = self.get_method(key)
2619
 
            if not isinstance(method, str):
2620
 
                raise TypeError(method)
2621
2554
            parents = parent_map[key]
2622
2555
            if method == 'fulltext':
2623
2556
                compression_parent = None
2624
2557
            else:
2625
2558
                compression_parent = parents[0]
2626
 
            noeol = b'no-eol' in self.get_options(key)
 
2559
            noeol = 'no-eol' in self.get_options(key)
2627
2560
            index_memo = self.get_position(key)
2628
2561
            result[key] = (index_memo, compression_parent,
2629
 
                           parents, (method, noeol))
 
2562
                                  parents, (method, noeol))
2630
2563
        return result
2631
2564
 
2632
2565
    def get_method(self, key):
2633
2566
        """Return compression method of specified key."""
2634
2567
        options = self.get_options(key)
2635
 
        if b'fulltext' in options:
 
2568
        if 'fulltext' in options:
2636
2569
            return 'fulltext'
2637
 
        elif b'line-delta' in options:
 
2570
        elif 'line-delta' in options:
2638
2571
            return 'line-delta'
2639
2572
        else:
2640
 
            raise KnitIndexUnknownMethod(self, options)
 
2573
            raise errors.KnitIndexUnknownMethod(self, options)
2641
2574
 
2642
2575
    def get_options(self, key):
2643
2576
        """Return a list representing options.
2675
2608
                                     for suffix in suffix_parents])
2676
2609
                parent_map[key] = parent_keys
2677
2610
                pending_keys.extend([p for p in parent_keys
2678
 
                                     if p not in parent_map])
 
2611
                                        if p not in parent_map])
2679
2612
        return parent_map, missing_keys
2680
2613
 
2681
2614
    def get_parent_map(self, keys):
2699
2632
                pass
2700
2633
            else:
2701
2634
                result[key] = tuple(prefix + (suffix,) for
2702
 
                                    suffix in suffix_parents)
 
2635
                    suffix in suffix_parents)
2703
2636
        return result
2704
2637
 
2705
2638
    def get_position(self, key):
2713
2646
        entry = self._kndx_cache[prefix][0][suffix]
2714
2647
        return key, entry[2], entry[3]
2715
2648
 
2716
 
    __contains__ = _mod_index._has_key_from_parent_map
 
2649
    has_key = _mod_index._has_key_from_parent_map
2717
2650
 
2718
2651
    def _init_index(self, path, extra_lines=[]):
2719
2652
        """Initialize an index."""
2720
 
        sio = BytesIO()
 
2653
        sio = StringIO()
2721
2654
        sio.write(self.HEADER)
2722
2655
        sio.writelines(extra_lines)
2723
2656
        sio.seek(0)
2724
2657
        self._transport.put_file_non_atomic(path, sio,
2725
 
                                            create_parent_dir=True)
2726
 
        # self._create_parent_dir)
2727
 
        # mode=self._file_mode,
2728
 
        # dir_mode=self._dir_mode)
 
2658
                            create_parent_dir=True)
 
2659
                           # self._create_parent_dir)
 
2660
                           # mode=self._file_mode,
 
2661
                           # dir_mode=self._dir_mode)
2729
2662
 
2730
2663
    def keys(self):
2731
2664
        """Get all the keys in the collection.
2735
2668
        result = set()
2736
2669
        # Identify all key prefixes.
2737
2670
        # XXX: A bit hacky, needs polish.
2738
 
        if isinstance(self._mapper, ConstantMapper):
 
2671
        if type(self._mapper) is ConstantMapper:
2739
2672
            prefixes = [()]
2740
2673
        else:
2741
2674
            relpaths = set()
2760
2693
                self._filename = prefix
2761
2694
                try:
2762
2695
                    path = self._mapper.map(prefix) + '.kndx'
2763
 
                    with self._transport.get(path) as fp:
 
2696
                    fp = self._transport.get(path)
 
2697
                    try:
2764
2698
                        # _load_data may raise NoSuchFile if the target knit is
2765
2699
                        # completely empty.
2766
2700
                        _load_data(self, fp)
 
2701
                    finally:
 
2702
                        fp.close()
2767
2703
                    self._kndx_cache[prefix] = (self._cache, self._history)
2768
2704
                    del self._cache
2769
2705
                    del self._filename
2770
2706
                    del self._history
2771
2707
                except NoSuchFile:
2772
2708
                    self._kndx_cache[prefix] = ({}, [])
2773
 
                    if isinstance(self._mapper, ConstantMapper):
 
2709
                    if type(self._mapper) is ConstantMapper:
2774
2710
                        # preserve behaviour for revisions.kndx etc.
2775
2711
                        self._init_index(path)
2776
2712
                    del self._cache
2796
2732
            '.' prefix.
2797
2733
        """
2798
2734
        if not keys:
2799
 
            return b''
 
2735
            return ''
2800
2736
        result_list = []
2801
2737
        prefix = keys[0][:-1]
2802
2738
        cache = self._kndx_cache[prefix][0]
2806
2742
                raise ValueError("mismatched prefixes for %r" % keys)
2807
2743
            if key[-1] in cache:
2808
2744
                # -- inlined lookup() --
2809
 
                result_list.append(b'%d' % cache[key[-1]][5])
 
2745
                result_list.append(str(cache[key[-1]][5]))
2810
2746
                # -- end lookup () --
2811
2747
            else:
2812
 
                result_list.append(b'.' + key[-1])
2813
 
        return b' '.join(result_list)
 
2748
                result_list.append('.' + key[-1])
 
2749
        return ' '.join(result_list)
2814
2750
 
2815
2751
    def _reset_cache(self):
2816
2752
        # Possibly this should be a LRU cache. A dictionary from key_prefix to
2847
2783
 
2848
2784
    def _split_key(self, key):
2849
2785
        """Split key into a prefix and suffix."""
2850
 
        # GZ 2018-07-03: This is intentionally either a sequence or bytes?
2851
 
        if isinstance(key, bytes):
2852
 
            return key[:-1], key[-1:]
2853
2786
        return key[:-1], key[-1]
2854
2787
 
2855
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
 
2856
2847
class _KnitGraphIndex(object):
2857
2848
    """A KnitVersionedFiles index layered on GraphIndex."""
2858
2849
 
2859
2850
    def __init__(self, graph_index, is_locked, deltas=False, parents=True,
2860
 
                 add_callback=None, track_external_parent_refs=False):
 
2851
        add_callback=None, track_external_parent_refs=False):
2861
2852
        """Construct a KnitGraphIndex on a graph_index.
2862
2853
 
2863
 
        :param graph_index: An implementation of breezy.index.GraphIndex.
 
2854
        :param graph_index: An implementation of bzrlib.index.GraphIndex.
2864
2855
        :param is_locked: A callback to check whether the object should answer
2865
2856
            queries.
2866
2857
        :param deltas: Allow delta-compressed records.
2883
2874
            # XXX: TODO: Delta tree and parent graph should be conceptually
2884
2875
            # separate.
2885
2876
            raise KnitCorrupt(self, "Cannot do delta compression without "
2886
 
                              "parent tracking.")
 
2877
                "parent tracking.")
2887
2878
        self.has_graph = parents
2888
2879
        self._is_locked = is_locked
2889
2880
        self._missing_compression_parents = set()
2896
2887
        return "%s(%r)" % (self.__class__.__name__, self._graph_index)
2897
2888
 
2898
2889
    def add_records(self, records, random_id=False,
2899
 
                    missing_compression_parents=False):
 
2890
        missing_compression_parents=False):
2900
2891
        """Add multiple records to the index.
2901
2892
 
2902
2893
        This function does not insert data into the Immutable GraphIndex
2927
2918
                if key_dependencies is not None:
2928
2919
                    key_dependencies.add_references(key, parents)
2929
2920
            index, pos, size = access_memo
2930
 
            if b'no-eol' in options:
2931
 
                value = b'N'
 
2921
            if 'no-eol' in options:
 
2922
                value = 'N'
2932
2923
            else:
2933
 
                value = b' '
2934
 
            value += b"%d %d" % (pos, size)
 
2924
                value = ' '
 
2925
            value += "%d %d" % (pos, size)
2935
2926
            if not self._deltas:
2936
 
                if b'line-delta' in options:
2937
 
                    raise KnitCorrupt(
2938
 
                        self, "attempt to add line-delta in non-delta knit")
 
2927
                if 'line-delta' in options:
 
2928
                    raise KnitCorrupt(self, "attempt to add line-delta in non-delta knit")
2939
2929
            if self._parents:
2940
2930
                if self._deltas:
2941
 
                    if b'line-delta' in options:
 
2931
                    if 'line-delta' in options:
2942
2932
                        node_refs = (parents, (parents[0],))
2943
2933
                        if missing_compression_parents:
2944
2934
                            compression_parents.add(parents[0])
2949
2939
            else:
2950
2940
                if parents:
2951
2941
                    raise KnitCorrupt(self, "attempt to add node with parents "
2952
 
                                      "in parentless index.")
 
2942
                        "in parentless index.")
2953
2943
                node_refs = ()
2954
2944
            keys[key] = (value, node_refs)
2955
2945
        # check for dups
2960
2950
                # Sometimes these are passed as a list rather than a tuple
2961
2951
                passed = static_tuple.as_tuples(keys[key])
2962
2952
                passed_parents = passed[1][:1]
2963
 
                if (value[0:1] != keys[key][0][0:1]
2964
 
                        or parents != passed_parents):
 
2953
                if (value[0] != keys[key][0][0] or
 
2954
                    parents != passed_parents):
2965
2955
                    node_refs = static_tuple.as_tuples(node_refs)
2966
2956
                    raise KnitCorrupt(self, "inconsistent details in add_records"
2967
 
                                      ": %s %s" % ((value, node_refs), passed))
 
2957
                        ": %s %s" % ((value, node_refs), passed))
2968
2958
                del keys[key]
2969
2959
        result = []
2970
2960
        if self._parents:
2971
 
            for key, (value, node_refs) in viewitems(keys):
 
2961
            for key, (value, node_refs) in keys.iteritems():
2972
2962
                result.append((key, value, node_refs))
2973
2963
        else:
2974
 
            for key, (value, node_refs) in viewitems(keys):
 
2964
            for key, (value, node_refs) in keys.iteritems():
2975
2965
                result.append((key, value))
2976
2966
        self._add_callback(result)
2977
2967
        if missing_compression_parents:
3075
3065
                compression_parent_key = None
3076
3066
            else:
3077
3067
                compression_parent_key = self._compression_parent(entry)
3078
 
            noeol = (entry[2][0:1] == b'N')
 
3068
            noeol = (entry[2][0] == 'N')
3079
3069
            if compression_parent_key:
3080
3070
                method = 'line-delta'
3081
3071
            else:
3082
3072
                method = 'fulltext'
3083
3073
            result[key] = (self._node_to_position(entry),
3084
 
                           compression_parent_key, parents,
3085
 
                           (method, noeol))
 
3074
                                  compression_parent_key, parents,
 
3075
                                  (method, noeol))
3086
3076
        return result
3087
3077
 
3088
3078
    def _get_entries(self, keys, check_present=False):
3130
3120
        e.g. ['foo', 'bar']
3131
3121
        """
3132
3122
        node = self._get_node(key)
3133
 
        options = [self._get_method(node).encode('ascii')]
3134
 
        if node[2][0:1] == b'N':
3135
 
            options.append(b'no-eol')
 
3123
        options = [self._get_method(node)]
 
3124
        if node[2][0] == 'N':
 
3125
            options.append('no-eol')
3136
3126
        return options
3137
3127
 
3138
3128
    def find_ancestry(self, keys):
3166
3156
        node = self._get_node(key)
3167
3157
        return self._node_to_position(node)
3168
3158
 
3169
 
    __contains__ = _mod_index._has_key_from_parent_map
 
3159
    has_key = _mod_index._has_key_from_parent_map
3170
3160
 
3171
3161
    def keys(self):
3172
3162
        """Get all the keys in the collection.
3180
3170
 
3181
3171
    def _node_to_position(self, node):
3182
3172
        """Convert an index value to position details."""
3183
 
        bits = node[2][1:].split(b' ')
 
3173
        bits = node[2][1:].split(' ')
3184
3174
        return node[0], int(bits[0]), int(bits[1])
3185
3175
 
3186
3176
    def _sort_keys_by_io(self, keys, positions):
3231
3221
            opaque index memo. For _KnitKeyAccess the memo is (key, pos,
3232
3222
            length), where the key is the record key.
3233
3223
        """
3234
 
        if not isinstance(raw_data, bytes):
 
3224
        if type(raw_data) is not str:
3235
3225
            raise AssertionError(
3236
3226
                'data must be plain bytes was %s' % type(raw_data))
3237
3227
        result = []
3243
3233
            path = self._mapper.map(key)
3244
3234
            try:
3245
3235
                base = self._transport.append_bytes(path + '.knit',
3246
 
                                                    raw_data[offset:offset + size])
 
3236
                    raw_data[offset:offset+size])
3247
3237
            except errors.NoSuchFile:
3248
3238
                self._transport.mkdir(osutils.dirname(path))
3249
3239
                base = self._transport.append_bytes(path + '.knit',
3250
 
                                                    raw_data[offset:offset + size])
 
3240
                    raw_data[offset:offset+size])
3251
3241
            # if base == 0:
3252
3242
            # chmod.
3253
3243
            offset += size
3256
3246
 
3257
3247
    def flush(self):
3258
3248
        """Flush pending writes on this access object.
3259
 
 
 
3249
        
3260
3250
        For .knit files this is a no-op.
3261
3251
        """
3262
3252
        pass
3288
3278
                yield data
3289
3279
 
3290
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
 
3291
3424
def annotate_knit(knit, revision_id):
3292
3425
    """Annotate a knit with no cached annotations.
3293
3426
 
3336
3469
            passing to read_records_iter to start reading in the raw data from
3337
3470
            the pack file.
3338
3471
        """
3339
 
        pending = {key}
 
3472
        pending = set([key])
3340
3473
        records = []
3341
3474
        ann_keys = set()
3342
3475
        self._num_needed_children[key] = 1
3347
3480
            self._all_build_details.update(build_details)
3348
3481
            # new_nodes = self._vf._index._get_entries(this_iteration)
3349
3482
            pending = set()
3350
 
            for key, details in viewitems(build_details):
 
3483
            for key, details in build_details.iteritems():
3351
3484
                (index_memo, compression_parent, parent_keys,
3352
3485
                 record_details) = details
3353
3486
                self._parent_map[key] = parent_keys
3355
3488
                records.append((key, index_memo))
3356
3489
                # Do we actually need to check _annotated_lines?
3357
3490
                pending.update([p for p in parent_keys
3358
 
                                if p not in self._all_build_details])
 
3491
                                   if p not in self._all_build_details])
3359
3492
                if parent_keys:
3360
3493
                    for parent_key in parent_keys:
3361
3494
                        if parent_key in self._num_needed_children:
3368
3501
                    else:
3369
3502
                        self._num_compression_children[compression_parent] = 1
3370
3503
 
3371
 
            missing_versions = this_iteration.difference(build_details)
 
3504
            missing_versions = this_iteration.difference(build_details.keys())
3372
3505
            if missing_versions:
3373
3506
                for key in missing_versions:
3374
3507
                    if key in self._parent_map and key in self._text_cache:
3382
3515
                            else:
3383
3516
                                self._num_needed_children[parent_key] = 1
3384
3517
                        pending.update([p for p in parent_keys
3385
 
                                        if p not in self._all_build_details])
 
3518
                                           if p not in self._all_build_details])
3386
3519
                    else:
3387
3520
                        raise errors.RevisionNotPresent(key, self._vf)
3388
3521
        # Generally we will want to read the records in reverse order, because
3391
3524
        return records, ann_keys
3392
3525
 
3393
3526
    def _get_needed_texts(self, key, pb=None):
3394
 
        # if True or len(self._vf._immediate_fallback_vfs) > 0:
3395
 
        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:
3396
3529
            # If we have fallbacks, go to the generic path
3397
3530
            for v in annotate.Annotator._get_needed_texts(self, key, pb=pb):
3398
3531
                yield v
3401
3534
            try:
3402
3535
                records, ann_keys = self._get_build_graph(key)
3403
3536
                for idx, (sub_key, text, num_lines) in enumerate(
3404
 
                        self._extract_texts(records)):
 
3537
                                                self._extract_texts(records)):
3405
3538
                    if pb is not None:
3406
 
                        pb.update(gettext('annotating'), idx, len(records))
 
3539
                        pb.update('annotating', idx, len(records))
3407
3540
                    yield sub_key, text, num_lines
3408
3541
                for sub_key in ann_keys:
3409
3542
                    text = self._text_cache[sub_key]
3410
 
                    num_lines = len(text)  # bad assumption
 
3543
                    num_lines = len(text) # bad assumption
3411
3544
                    yield sub_key, text, num_lines
3412
3545
                return
3413
 
            except errors.RetryWithNewPacks as e:
 
3546
            except errors.RetryWithNewPacks, e:
3414
3547
                self._vf._access.reload_or_raise(e)
3415
3548
                # The cached build_details are no longer valid
3416
3549
                self._all_build_details.clear()
3417
3550
 
3418
3551
    def _cache_delta_blocks(self, key, compression_parent, delta, lines):
3419
3552
        parent_lines = self._text_cache[compression_parent]
3420
 
        blocks = list(KnitContent.get_line_delta_blocks(
3421
 
            delta, parent_lines, lines))
 
3553
        blocks = list(KnitContent.get_line_delta_blocks(delta, parent_lines, lines))
3422
3554
        self._matching_blocks[(key, compression_parent)] = blocks
3423
3555
 
3424
3556
    def _expand_record(self, key, parent_keys, compression_parent, record,
3477
3609
            parent_annotations = self._annotations_cache[parent_key]
3478
3610
            return parent_annotations, blocks
3479
3611
        return annotate.Annotator._get_parent_annotations_and_matches(self,
3480
 
                                                                      key, text, parent_key)
 
3612
            key, text, parent_key)
3481
3613
 
3482
3614
    def _process_pending(self, key):
3483
3615
        """The content for 'key' was just processed.
3514
3646
                # Note that if there are multiple parents, we need to wait
3515
3647
                # for all of them.
3516
3648
                self._pending_annotation.setdefault(parent_key,
3517
 
                                                    []).append((key, parent_keys))
 
3649
                    []).append((key, parent_keys))
3518
3650
                return False
3519
3651
        return True
3520
3652
 
3575
3707
                    yield key, lines, len(lines)
3576
3708
                    to_process.extend(self._process_pending(key))
3577
3709
 
3578
 
 
3579
3710
try:
3580
 
    from ._knit_load_data_pyx import _load_data_c as _load_data
3581
 
except ImportError as e:
 
3711
    from bzrlib._knit_load_data_pyx import _load_data_c as _load_data
 
3712
except ImportError, e:
3582
3713
    osutils.failed_to_load_extension(e)
3583
 
    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