/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: Richard Wilbur
  • Date: 2016-02-04 19:07:28 UTC
  • mto: This revision was merged to the branch mainline in revision 6618.
  • Revision ID: richard.wilbur@gmail.com-20160204190728-p0zvfii6zase0fw7
Update COPYING.txt from the original http://www.gnu.org/licenses/gpl-2.0.txt  (Only differences were in whitespace.)  Thanks to Petr Stodulka for pointing out the discrepancy.

Show diffs side-by-side

added added

removed removed

Lines of Context:
51
51
 
52
52
"""
53
53
 
 
54
from __future__ import absolute_import
 
55
 
 
56
 
 
57
from cStringIO import StringIO
 
58
from itertools import izip
54
59
import operator
55
 
from io import BytesIO
56
60
import os
57
61
 
58
 
from ..lazy_import import lazy_import
 
62
from bzrlib.lazy_import import lazy_import
59
63
lazy_import(globals(), """
60
 
import patiencediff
61
64
import gzip
62
65
 
63
 
from breezy import (
 
66
from bzrlib import (
64
67
    debug,
65
68
    diff,
 
69
    graph as _mod_graph,
 
70
    index as _mod_index,
 
71
    pack,
 
72
    patiencediff,
66
73
    static_tuple,
67
74
    trace,
68
75
    tsort,
69
76
    tuned_gzip,
70
77
    ui,
71
78
    )
72
 
from breezy.bzr import (
73
 
    index as _mod_index,
74
 
    pack,
75
 
    )
76
79
 
77
 
from breezy.bzr import pack_repo
78
 
from breezy.i18n import gettext
 
80
from bzrlib.repofmt import pack_repo
 
81
from bzrlib.i18n import gettext
79
82
""")
80
 
from .. import (
 
83
from bzrlib import (
81
84
    annotate,
82
85
    errors,
83
86
    osutils,
84
87
    )
85
 
from ..errors import (
86
 
    InternalBzrError,
 
88
from bzrlib.errors import (
 
89
    NoSuchFile,
87
90
    InvalidRevisionId,
88
 
    NoSuchFile,
 
91
    KnitCorrupt,
 
92
    KnitHeaderError,
89
93
    RevisionNotPresent,
 
94
    SHA1KnitCorrupt,
90
95
    )
91
 
from ..osutils import (
 
96
from bzrlib.osutils import (
92
97
    contains_whitespace,
93
98
    sha_string,
94
99
    sha_strings,
95
100
    split_lines,
96
101
    )
97
 
from ..bzr.versionedfile import (
 
102
from bzrlib.versionedfile import (
98
103
    _KeyRefs,
99
104
    AbsentContentFactory,
100
105
    adapter_registry,
101
106
    ConstantMapper,
102
107
    ContentFactory,
103
 
    ExistingContent,
104
108
    sort_groupcompress,
105
 
    UnavailableRepresentation,
106
109
    VersionedFilesWithFallbacks,
107
110
    )
108
111
 
122
125
 
123
126
DATA_SUFFIX = '.knit'
124
127
INDEX_SUFFIX = '.kndx'
125
 
_STREAM_MIN_BUFFER_SIZE = 5 * 1024 * 1024
126
 
 
127
 
 
128
 
class KnitError(InternalBzrError):
129
 
 
130
 
    _fmt = "Knit error"
131
 
 
132
 
 
133
 
class KnitCorrupt(KnitError):
134
 
 
135
 
    _fmt = "Knit %(filename)s corrupt: %(how)s"
136
 
 
137
 
    def __init__(self, filename, how):
138
 
        KnitError.__init__(self)
139
 
        self.filename = filename
140
 
        self.how = how
141
 
 
142
 
 
143
 
class SHA1KnitCorrupt(KnitCorrupt):
144
 
 
145
 
    _fmt = ("Knit %(filename)s corrupt: sha-1 of reconstructed text does not "
146
 
            "match expected sha-1. key %(key)s expected sha %(expected)s actual "
147
 
            "sha %(actual)s")
148
 
 
149
 
    def __init__(self, filename, actual, expected, key, content):
150
 
        KnitError.__init__(self)
151
 
        self.filename = filename
152
 
        self.actual = actual
153
 
        self.expected = expected
154
 
        self.key = key
155
 
        self.content = content
156
 
 
157
 
 
158
 
class KnitDataStreamIncompatible(KnitError):
159
 
    # Not raised anymore, as we can convert data streams.  In future we may
160
 
    # need it again for more exotic cases, so we're keeping it around for now.
161
 
 
162
 
    _fmt = "Cannot insert knit data stream of format \"%(stream_format)s\" into knit of format \"%(target_format)s\"."
163
 
 
164
 
    def __init__(self, stream_format, target_format):
165
 
        self.stream_format = stream_format
166
 
        self.target_format = target_format
167
 
 
168
 
 
169
 
class KnitDataStreamUnknown(KnitError):
170
 
    # Indicates a data stream we don't know how to handle.
171
 
 
172
 
    _fmt = "Cannot parse knit data stream of format \"%(stream_format)s\"."
173
 
 
174
 
    def __init__(self, stream_format):
175
 
        self.stream_format = stream_format
176
 
 
177
 
 
178
 
class KnitHeaderError(KnitError):
179
 
 
180
 
    _fmt = 'Knit header error: %(badline)r unexpected for file "%(filename)s".'
181
 
 
182
 
    def __init__(self, badline, filename):
183
 
        KnitError.__init__(self)
184
 
        self.badline = badline
185
 
        self.filename = filename
186
 
 
187
 
 
188
 
class KnitIndexUnknownMethod(KnitError):
189
 
    """Raised when we don't understand the storage method.
190
 
 
191
 
    Currently only 'fulltext' and 'line-delta' are supported.
192
 
    """
193
 
 
194
 
    _fmt = ("Knit index %(filename)s does not have a known method"
195
 
            " in options: %(options)r")
196
 
 
197
 
    def __init__(self, filename, options):
198
 
        KnitError.__init__(self)
199
 
        self.filename = filename
200
 
        self.options = options
 
128
_STREAM_MIN_BUFFER_SIZE = 5*1024*1024
201
129
 
202
130
 
203
131
class KnitAdapter(object):
218
146
class FTAnnotatedToUnannotated(KnitAdapter):
219
147
    """An adapter from FT annotated knits to unannotated ones."""
220
148
 
221
 
    def get_bytes(self, factory, target_storage_kind):
222
 
        if target_storage_kind != 'knit-ft-gz':
223
 
            raise UnavailableRepresentation(
224
 
                factory.key, target_storage_kind, factory.storage_kind)
 
149
    def get_bytes(self, factory):
225
150
        annotated_compressed_bytes = factory._raw_record
226
151
        rec, contents = \
227
152
            self._data._parse_record_unchecked(annotated_compressed_bytes)
228
153
        content = self._annotate_factory.parse_fulltext(contents, rec[1])
229
 
        size, chunks = self._data._record_to_data(
230
 
            (rec[1],), rec[3], content.text())
231
 
        return b''.join(chunks)
 
154
        size, bytes = self._data._record_to_data((rec[1],), rec[3], content.text())
 
155
        return bytes
232
156
 
233
157
 
234
158
class DeltaAnnotatedToUnannotated(KnitAdapter):
235
159
    """An adapter for deltas from annotated to unannotated."""
236
160
 
237
 
    def get_bytes(self, factory, target_storage_kind):
238
 
        if target_storage_kind != 'knit-delta-gz':
239
 
            raise UnavailableRepresentation(
240
 
                factory.key, target_storage_kind, factory.storage_kind)
 
161
    def get_bytes(self, factory):
241
162
        annotated_compressed_bytes = factory._raw_record
242
163
        rec, contents = \
243
164
            self._data._parse_record_unchecked(annotated_compressed_bytes)
244
165
        delta = self._annotate_factory.parse_line_delta(contents, rec[1],
245
 
                                                        plain=True)
 
166
            plain=True)
246
167
        contents = self._plain_factory.lower_line_delta(delta)
247
 
        size, chunks = self._data._record_to_data((rec[1],), rec[3], contents)
248
 
        return b''.join(chunks)
 
168
        size, bytes = self._data._record_to_data((rec[1],), rec[3], contents)
 
169
        return bytes
249
170
 
250
171
 
251
172
class FTAnnotatedToFullText(KnitAdapter):
252
173
    """An adapter from FT annotated knits to unannotated ones."""
253
174
 
254
 
    def get_bytes(self, factory, target_storage_kind):
 
175
    def get_bytes(self, factory):
255
176
        annotated_compressed_bytes = factory._raw_record
256
177
        rec, contents = \
257
178
            self._data._parse_record_unchecked(annotated_compressed_bytes)
258
179
        content, delta = self._annotate_factory.parse_record(factory.key[-1],
259
 
                                                             contents, factory._build_details, None)
260
 
        if target_storage_kind == 'fulltext':
261
 
            return b''.join(content.text())
262
 
        elif target_storage_kind in ('chunked', 'lines'):
263
 
            return content.text()
264
 
        raise UnavailableRepresentation(
265
 
            factory.key, target_storage_kind, factory.storage_kind)
 
180
            contents, factory._build_details, None)
 
181
        return ''.join(content.text())
266
182
 
267
183
 
268
184
class DeltaAnnotatedToFullText(KnitAdapter):
269
185
    """An adapter for deltas from annotated to unannotated."""
270
186
 
271
 
    def get_bytes(self, factory, target_storage_kind):
 
187
    def get_bytes(self, factory):
272
188
        annotated_compressed_bytes = factory._raw_record
273
189
        rec, contents = \
274
190
            self._data._parse_record_unchecked(annotated_compressed_bytes)
275
191
        delta = self._annotate_factory.parse_line_delta(contents, rec[1],
276
 
                                                        plain=True)
 
192
            plain=True)
277
193
        compression_parent = factory.parents[0]
278
 
        basis_entry = next(self._basis_vf.get_record_stream(
279
 
            [compression_parent], 'unordered', True))
 
194
        basis_entry = self._basis_vf.get_record_stream(
 
195
            [compression_parent], 'unordered', True).next()
280
196
        if basis_entry.storage_kind == 'absent':
281
197
            raise errors.RevisionNotPresent(compression_parent, self._basis_vf)
282
 
        basis_lines = basis_entry.get_bytes_as('lines')
 
198
        basis_chunks = basis_entry.get_bytes_as('chunked')
 
199
        basis_lines = osutils.chunks_to_lines(basis_chunks)
283
200
        # Manually apply the delta because we have one annotated content and
284
201
        # one plain.
285
202
        basis_content = PlainKnitContent(basis_lines, compression_parent)
286
203
        basis_content.apply_delta(delta, rec[1])
287
204
        basis_content._should_strip_eol = factory._build_details[1]
288
 
 
289
 
        if target_storage_kind == 'fulltext':
290
 
            return b''.join(basis_content.text())
291
 
        elif target_storage_kind in ('chunked', 'lines'):
292
 
            return basis_content.text()
293
 
        raise UnavailableRepresentation(
294
 
            factory.key, target_storage_kind, factory.storage_kind)
 
205
        return ''.join(basis_content.text())
295
206
 
296
207
 
297
208
class FTPlainToFullText(KnitAdapter):
298
209
    """An adapter from FT plain knits to unannotated ones."""
299
210
 
300
 
    def get_bytes(self, factory, target_storage_kind):
 
211
    def get_bytes(self, factory):
301
212
        compressed_bytes = factory._raw_record
302
213
        rec, contents = \
303
214
            self._data._parse_record_unchecked(compressed_bytes)
304
215
        content, delta = self._plain_factory.parse_record(factory.key[-1],
305
 
                                                          contents, factory._build_details, None)
306
 
        if target_storage_kind == 'fulltext':
307
 
            return b''.join(content.text())
308
 
        elif target_storage_kind in ('chunked', 'lines'):
309
 
            return content.text()
310
 
        raise UnavailableRepresentation(
311
 
            factory.key, target_storage_kind, factory.storage_kind)
 
216
            contents, factory._build_details, None)
 
217
        return ''.join(content.text())
312
218
 
313
219
 
314
220
class DeltaPlainToFullText(KnitAdapter):
315
221
    """An adapter for deltas from annotated to unannotated."""
316
222
 
317
 
    def get_bytes(self, factory, target_storage_kind):
 
223
    def get_bytes(self, factory):
318
224
        compressed_bytes = factory._raw_record
319
225
        rec, contents = \
320
226
            self._data._parse_record_unchecked(compressed_bytes)
321
227
        delta = self._plain_factory.parse_line_delta(contents, rec[1])
322
228
        compression_parent = factory.parents[0]
323
229
        # XXX: string splitting overhead.
324
 
        basis_entry = next(self._basis_vf.get_record_stream(
325
 
            [compression_parent], 'unordered', True))
 
230
        basis_entry = self._basis_vf.get_record_stream(
 
231
            [compression_parent], 'unordered', True).next()
326
232
        if basis_entry.storage_kind == 'absent':
327
233
            raise errors.RevisionNotPresent(compression_parent, self._basis_vf)
328
 
        basis_lines = basis_entry.get_bytes_as('lines')
 
234
        basis_chunks = basis_entry.get_bytes_as('chunked')
 
235
        basis_lines = osutils.chunks_to_lines(basis_chunks)
329
236
        basis_content = PlainKnitContent(basis_lines, compression_parent)
330
237
        # Manually apply the delta because we have one annotated content and
331
238
        # one plain.
332
239
        content, _ = self._plain_factory.parse_record(rec[1], contents,
333
 
                                                      factory._build_details, basis_content)
334
 
        if target_storage_kind == 'fulltext':
335
 
            return b''.join(content.text())
336
 
        elif target_storage_kind in ('chunked', 'lines'):
337
 
            return content.text()
338
 
        raise UnavailableRepresentation(
339
 
            factory.key, target_storage_kind, factory.storage_kind)
 
240
            factory._build_details, basis_content)
 
241
        return ''.join(content.text())
340
242
 
341
243
 
342
244
class KnitContentFactory(ContentFactory):
346
248
    """
347
249
 
348
250
    def __init__(self, key, parents, build_details, sha1, raw_record,
349
 
                 annotated, knit=None, network_bytes=None):
 
251
        annotated, knit=None, network_bytes=None):
350
252
        """Create a KnitContentFactory for key.
351
253
 
352
254
        :param key: The key.
380
282
    def _create_network_bytes(self):
381
283
        """Create a fully serialised network version for transmission."""
382
284
        # storage_kind, key, parents, Noeol, raw_record
383
 
        key_bytes = b'\x00'.join(self.key)
 
285
        key_bytes = '\x00'.join(self.key)
384
286
        if self.parents is None:
385
 
            parent_bytes = b'None:'
 
287
            parent_bytes = 'None:'
386
288
        else:
387
 
            parent_bytes = b'\t'.join(b'\x00'.join(key)
388
 
                                      for key in self.parents)
 
289
            parent_bytes = '\t'.join('\x00'.join(key) for key in self.parents)
389
290
        if self._build_details[1]:
390
 
            noeol = b'N'
 
291
            noeol = 'N'
391
292
        else:
392
 
            noeol = b' '
393
 
        network_bytes = b"%s\n%s\n%s\n%s%s" % (
394
 
            self.storage_kind.encode('ascii'), key_bytes,
 
293
            noeol = ' '
 
294
        network_bytes = "%s\n%s\n%s\n%s%s" % (self.storage_kind, key_bytes,
395
295
            parent_bytes, noeol, self._raw_record)
396
296
        self._network_bytes = network_bytes
397
297
 
400
300
            if self._network_bytes is None:
401
301
                self._create_network_bytes()
402
302
            return self._network_bytes
403
 
        if ('-ft-' in self.storage_kind
404
 
                and storage_kind in ('chunked', 'fulltext', 'lines')):
405
 
            adapter_key = (self.storage_kind, storage_kind)
 
303
        if ('-ft-' in self.storage_kind and
 
304
            storage_kind in ('chunked', 'fulltext')):
 
305
            adapter_key = (self.storage_kind, 'fulltext')
406
306
            adapter_factory = adapter_registry.get(adapter_key)
407
307
            adapter = adapter_factory(None)
408
 
            return adapter.get_bytes(self, storage_kind)
 
308
            bytes = adapter.get_bytes(self)
 
309
            if storage_kind == 'chunked':
 
310
                return [bytes]
 
311
            else:
 
312
                return bytes
409
313
        if self._knit is not None:
410
314
            # Not redundant with direct conversion above - that only handles
411
315
            # fulltext cases.
412
 
            if storage_kind in ('chunked', 'lines'):
 
316
            if storage_kind == 'chunked':
413
317
                return self._knit.get_lines(self.key[0])
414
318
            elif storage_kind == 'fulltext':
415
319
                return self._knit.get_text(self.key[0])
416
 
        raise UnavailableRepresentation(self.key, storage_kind,
417
 
                                               self.storage_kind)
418
 
 
419
 
    def iter_bytes_as(self, storage_kind):
420
 
        return iter(self.get_bytes_as(storage_kind))
 
320
        raise errors.UnavailableRepresentation(self.key, storage_kind,
 
321
            self.storage_kind)
421
322
 
422
323
 
423
324
class LazyKnitContentFactory(ContentFactory):
440
341
        self.key = key
441
342
        self.parents = parents
442
343
        self.sha1 = None
443
 
        self.size = None
444
344
        self._generator = generator
445
345
        self.storage_kind = "knit-delta-closure"
446
346
        if not first:
454
354
            else:
455
355
                # all the keys etc are contained in the bytes returned in the
456
356
                # first record.
457
 
                return b''
458
 
        if storage_kind in ('chunked', 'fulltext', 'lines'):
 
357
                return ''
 
358
        if storage_kind in ('chunked', 'fulltext'):
459
359
            chunks = self._generator._get_one_work(self.key).text()
460
 
            if storage_kind in ('chunked', 'lines'):
 
360
            if storage_kind == 'chunked':
461
361
                return chunks
462
362
            else:
463
 
                return b''.join(chunks)
464
 
        raise UnavailableRepresentation(self.key, storage_kind,
465
 
                                               self.storage_kind)
466
 
 
467
 
    def iter_bytes_as(self, storage_kind):
468
 
        if storage_kind in ('chunked', 'lines'):
469
 
            chunks = self._generator._get_one_work(self.key).text()
470
 
            return iter(chunks)
 
363
                return ''.join(chunks)
471
364
        raise errors.UnavailableRepresentation(self.key, storage_kind,
472
 
                                               self.storage_kind)
 
365
            self.storage_kind)
473
366
 
474
367
 
475
368
def knit_delta_closure_to_records(storage_kind, bytes, line_end):
490
383
    :param bytes: The bytes of the record on the network.
491
384
    """
492
385
    start = line_end
493
 
    line_end = bytes.find(b'\n', start)
494
 
    key = tuple(bytes[start:line_end].split(b'\x00'))
 
386
    line_end = bytes.find('\n', start)
 
387
    key = tuple(bytes[start:line_end].split('\x00'))
495
388
    start = line_end + 1
496
 
    line_end = bytes.find(b'\n', start)
 
389
    line_end = bytes.find('\n', start)
497
390
    parent_line = bytes[start:line_end]
498
 
    if parent_line == b'None:':
 
391
    if parent_line == 'None:':
499
392
        parents = None
500
393
    else:
501
394
        parents = tuple(
502
 
            [tuple(segment.split(b'\x00')) for segment in parent_line.split(b'\t')
 
395
            [tuple(segment.split('\x00')) for segment in parent_line.split('\t')
503
396
             if segment])
504
397
    start = line_end + 1
505
 
    noeol = bytes[start:start + 1] == b'N'
 
398
    noeol = bytes[start] == 'N'
506
399
    if 'ft' in storage_kind:
507
400
        method = 'fulltext'
508
401
    else:
512
405
    raw_record = bytes[start:]
513
406
    annotated = 'annotated' in storage_kind
514
407
    return [KnitContentFactory(key, parents, build_details, None, raw_record,
515
 
                               annotated, network_bytes=bytes)]
 
408
        annotated, network_bytes=bytes)]
516
409
 
517
410
 
518
411
class KnitContent(object):
556
449
            if n > 0:
557
450
                # knit deltas do not provide reliable info about whether the
558
451
                # last line of a file matches, due to eol handling.
559
 
                if source[s_pos + n - 1] != target[t_pos + n - 1]:
560
 
                    n -= 1
 
452
                if source[s_pos + n -1] != target[t_pos + n -1]:
 
453
                    n-=1
561
454
                if n > 0:
562
455
                    yield s_pos, t_pos, n
563
456
            t_pos += t_len + true_n
564
457
            s_pos = s_end
565
458
        n = target_len - t_pos
566
459
        if n > 0:
567
 
            if source[s_pos + n - 1] != target[t_pos + n - 1]:
568
 
                n -= 1
 
460
            if source[s_pos + n -1] != target[t_pos + n -1]:
 
461
                n-=1
569
462
            if n > 0:
570
463
                yield s_pos, t_pos, n
571
464
        yield s_pos + (target_len - t_pos), target_len, 0
576
469
 
577
470
    def __init__(self, lines):
578
471
        KnitContent.__init__(self)
579
 
        self._lines = list(lines)
 
472
        self._lines = lines
580
473
 
581
474
    def annotate(self):
582
475
        """Return a list of (origin, text) for each content line."""
583
476
        lines = self._lines[:]
584
477
        if self._should_strip_eol:
585
478
            origin, last_line = lines[-1]
586
 
            lines[-1] = (origin, last_line.rstrip(b'\n'))
 
479
            lines[-1] = (origin, last_line.rstrip('\n'))
587
480
        return lines
588
481
 
589
482
    def apply_delta(self, delta, new_version_id):
591
484
        offset = 0
592
485
        lines = self._lines
593
486
        for start, end, count, delta_lines in delta:
594
 
            lines[offset + start:offset + end] = delta_lines
 
487
            lines[offset+start:offset+end] = delta_lines
595
488
            offset = offset + (start - end) + count
596
489
 
597
490
    def text(self):
598
491
        try:
599
492
            lines = [text for origin, text in self._lines]
600
 
        except ValueError as e:
 
493
        except ValueError, e:
601
494
            # most commonly (only?) caused by the internal form of the knit
602
495
            # missing annotation information because of a bug - see thread
603
496
            # around 20071015
604
497
            raise KnitCorrupt(self,
605
 
                              "line in annotated knit missing annotation information: %s"
606
 
                              % (e,))
 
498
                "line in annotated knit missing annotation information: %s"
 
499
                % (e,))
607
500
        if self._should_strip_eol:
608
 
            lines[-1] = lines[-1].rstrip(b'\n')
 
501
            lines[-1] = lines[-1].rstrip('\n')
609
502
        return lines
610
503
 
611
504
    def copy(self):
612
 
        return AnnotatedKnitContent(self._lines)
 
505
        return AnnotatedKnitContent(self._lines[:])
613
506
 
614
507
 
615
508
class PlainKnitContent(KnitContent):
634
527
        offset = 0
635
528
        lines = self._lines
636
529
        for start, end, count, delta_lines in delta:
637
 
            lines[offset + start:offset + end] = delta_lines
 
530
            lines[offset+start:offset+end] = delta_lines
638
531
            offset = offset + (start - end) + count
639
532
        self._version_id = new_version_id
640
533
 
645
538
        lines = self._lines
646
539
        if self._should_strip_eol:
647
540
            lines = lines[:]
648
 
            lines[-1] = lines[-1].rstrip(b'\n')
 
541
            lines[-1] = lines[-1].rstrip('\n')
649
542
        return lines
650
543
 
651
544
 
704
597
        #       but the code itself doesn't really depend on that.
705
598
        #       Figure out a way to not require the overhead of turning the
706
599
        #       list back into tuples.
707
 
        lines = (tuple(line.split(b' ', 1)) for line in content)
 
600
        lines = [tuple(line.split(' ', 1)) for line in content]
708
601
        return AnnotatedKnitContent(lines)
709
602
 
 
603
    def parse_line_delta_iter(self, lines):
 
604
        return iter(self.parse_line_delta(lines))
 
605
 
710
606
    def parse_line_delta(self, lines, version_id, plain=False):
711
607
        """Convert a line based delta into internal representation.
712
608
 
723
619
        """
724
620
        result = []
725
621
        lines = iter(lines)
 
622
        next = lines.next
726
623
 
727
624
        cache = {}
728
 
 
729
625
        def cache_and_return(line):
730
 
            origin, text = line.split(b' ', 1)
 
626
            origin, text = line.split(' ', 1)
731
627
            return cache.setdefault(origin, origin), text
732
628
 
733
629
        # walk through the lines parsing.
735
631
        # loop to minimise any performance impact
736
632
        if plain:
737
633
            for header in lines:
738
 
                start, end, count = [int(n) for n in header.split(b',')]
739
 
                contents = [next(lines).split(b' ', 1)[1]
740
 
                            for _ in range(count)]
 
634
                start, end, count = [int(n) for n in header.split(',')]
 
635
                contents = [next().split(' ', 1)[1] for i in xrange(count)]
741
636
                result.append((start, end, count, contents))
742
637
        else:
743
638
            for header in lines:
744
 
                start, end, count = [int(n) for n in header.split(b',')]
745
 
                contents = [tuple(next(lines).split(b' ', 1))
746
 
                            for _ in range(count)]
 
639
                start, end, count = [int(n) for n in header.split(',')]
 
640
                contents = [tuple(next().split(' ', 1)) for i in xrange(count)]
747
641
                result.append((start, end, count, contents))
748
642
        return result
749
643
 
750
644
    def get_fulltext_content(self, lines):
751
645
        """Extract just the content lines from a fulltext."""
752
 
        return (line.split(b' ', 1)[1] for line in lines)
 
646
        return (line.split(' ', 1)[1] for line in lines)
753
647
 
754
648
    def get_linedelta_content(self, lines):
755
649
        """Extract just the content from a line delta.
758
652
        Only the actual content lines.
759
653
        """
760
654
        lines = iter(lines)
 
655
        next = lines.next
761
656
        for header in lines:
762
 
            header = header.split(b',')
 
657
            header = header.split(',')
763
658
            count = int(header[2])
764
 
            for _ in range(count):
765
 
                origin, text = next(lines).split(b' ', 1)
 
659
            for i in xrange(count):
 
660
                origin, text = next().split(' ', 1)
766
661
                yield text
767
662
 
768
663
    def lower_fulltext(self, content):
770
665
 
771
666
        see parse_fulltext which this inverts.
772
667
        """
773
 
        return [b'%s %s' % (o, t) for o, t in content._lines]
 
668
        return ['%s %s' % (o, t) for o, t in content._lines]
774
669
 
775
670
    def lower_line_delta(self, delta):
776
671
        """convert a delta into a serializable form.
781
676
        #       the origin is a valid utf-8 line, eventually we could remove it
782
677
        out = []
783
678
        for start, end, c, lines in delta:
784
 
            out.append(b'%d,%d,%d\n' % (start, end, c))
785
 
            out.extend(origin + b' ' + text
 
679
            out.append('%d,%d,%d\n' % (start, end, c))
 
680
            out.extend(origin + ' ' + text
786
681
                       for origin, text in lines)
787
682
        return out
788
683
 
790
685
        content = knit._get_content(key)
791
686
        # adjust for the fact that serialised annotations are only key suffixes
792
687
        # for this factory.
793
 
        if isinstance(key, tuple):
 
688
        if type(key) is tuple:
794
689
            prefix = key[:-1]
795
690
            origins = content.annotate()
796
691
            result = []
825
720
        while cur < num_lines:
826
721
            header = lines[cur]
827
722
            cur += 1
828
 
            start, end, c = [int(n) for n in header.split(b',')]
829
 
            yield start, end, c, lines[cur:cur + c]
 
723
            start, end, c = [int(n) for n in header.split(',')]
 
724
            yield start, end, c, lines[cur:cur+c]
830
725
            cur += c
831
726
 
832
727
    def parse_line_delta(self, lines, version_id):
843
738
        Only the actual content lines.
844
739
        """
845
740
        lines = iter(lines)
 
741
        next = lines.next
846
742
        for header in lines:
847
 
            header = header.split(b',')
 
743
            header = header.split(',')
848
744
            count = int(header[2])
849
 
            for _ in range(count):
850
 
                yield next(lines)
 
745
            for i in xrange(count):
 
746
                yield next()
851
747
 
852
748
    def lower_fulltext(self, content):
853
749
        return content.text()
855
751
    def lower_line_delta(self, delta):
856
752
        out = []
857
753
        for start, end, c, lines in delta:
858
 
            out.append(b'%d,%d,%d\n' % (start, end, c))
 
754
            out.append('%d,%d,%d\n' % (start, end, c))
859
755
            out.extend(lines)
860
756
        return out
861
757
 
864
760
        return annotator.annotate_flat(key)
865
761
 
866
762
 
 
763
 
867
764
def make_file_factory(annotated, mapper):
868
765
    """Create a factory for creating a file based KnitVersionedFiles.
869
766
 
874
771
    :param mapper: The mapper from keys to paths.
875
772
    """
876
773
    def factory(transport):
877
 
        index = _KndxIndex(transport, mapper, lambda: None,
878
 
                           lambda: True, lambda: True)
 
774
        index = _KndxIndex(transport, mapper, lambda:None, lambda:True, lambda:True)
879
775
        access = _KnitKeyAccess(transport, mapper)
880
776
        return KnitVersionedFiles(index, access, annotated=annotated)
881
777
    return factory
902
798
        else:
903
799
            max_delta_chain = 0
904
800
        graph_index = _mod_index.InMemoryGraphIndex(reference_lists=ref_length,
905
 
                                                    key_elements=keylength)
 
801
            key_elements=keylength)
906
802
        stream = transport.open_write_stream('newpack')
907
803
        writer = pack.ContainerWriter(stream.write)
908
804
        writer.begin()
909
 
        index = _KnitGraphIndex(graph_index, lambda: True, parents=parents,
910
 
                                deltas=delta, add_callback=graph_index.add_nodes)
 
805
        index = _KnitGraphIndex(graph_index, lambda:True, parents=parents,
 
806
            deltas=delta, add_callback=graph_index.add_nodes)
911
807
        access = pack_repo._DirectPackAccess({})
912
808
        access.set_writer(writer, graph_index, (transport, 'newpack'))
913
809
        result = KnitVersionedFiles(index, access,
914
 
                                    max_delta_chain=max_delta_chain)
 
810
            max_delta_chain=max_delta_chain)
915
811
        result.stream = stream
916
812
        result.writer = writer
917
813
        return result
948
844
            if compression_parent not in all_build_index_memos:
949
845
                next_keys.add(compression_parent)
950
846
        build_keys = next_keys
951
 
    return sum(index_memo[2]
952
 
               for index_memo in all_build_index_memos.values())
 
847
    return sum([index_memo[2] for index_memo
 
848
                in all_build_index_memos.itervalues()])
953
849
 
954
850
 
955
851
class KnitVersionedFiles(VersionedFilesWithFallbacks):
976
872
            stored during insertion.
977
873
        :param reload_func: An function that can be called if we think we need
978
874
            to reload the pack listing and try again. See
979
 
            'breezy.bzr.pack_repo.AggregateIndex' for the signature.
 
875
            'bzrlib.repofmt.pack_repo.AggregateIndex' for the signature.
980
876
        """
981
877
        self._index = index
982
878
        self._access = data_access
997
893
    def without_fallbacks(self):
998
894
        """Return a clone of this object without any fallbacks configured."""
999
895
        return KnitVersionedFiles(self._index, self._access,
1000
 
                                  self._max_delta_chain, self._factory.annotated,
1001
 
                                  self._reload_func)
 
896
            self._max_delta_chain, self._factory.annotated,
 
897
            self._reload_func)
1002
898
 
1003
899
    def add_fallback_versioned_files(self, a_versioned_files):
1004
900
        """Add a source of texts for texts not present in this knit.
1008
904
        self._immediate_fallback_vfs.append(a_versioned_files)
1009
905
 
1010
906
    def add_lines(self, key, parents, lines, parent_texts=None,
1011
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
1012
 
                  check_content=True):
 
907
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
908
        check_content=True):
1013
909
        """See VersionedFiles.add_lines()."""
1014
910
        self._index._check_write_ok()
1015
911
        self._check_add(key, lines, random_id, check_content)
1018
914
            # indexes can't directly store that, so we give them
1019
915
            # an empty tuple instead.
1020
916
            parents = ()
1021
 
        line_bytes = b''.join(lines)
 
917
        line_bytes = ''.join(lines)
1022
918
        return self._add(key, lines, parents,
1023
 
                         parent_texts, left_matching_blocks, nostore_sha, random_id,
1024
 
                         line_bytes=line_bytes)
 
919
            parent_texts, left_matching_blocks, nostore_sha, random_id,
 
920
            line_bytes=line_bytes)
1025
921
 
1026
 
    def add_content(self, content_factory, parent_texts=None,
1027
 
                    left_matching_blocks=None, nostore_sha=None,
1028
 
                    random_id=False):
1029
 
        """See VersionedFiles.add_content()."""
 
922
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
 
923
        """See VersionedFiles._add_text()."""
1030
924
        self._index._check_write_ok()
1031
 
        key = content_factory.key
1032
 
        parents = content_factory.parents
1033
925
        self._check_add(key, None, random_id, check_content=False)
 
926
        if text.__class__ is not str:
 
927
            raise errors.BzrBadParameterUnicode("text")
1034
928
        if parents is None:
1035
929
            # The caller might pass None if there is no graph data, but kndx
1036
930
            # indexes can't directly store that, so we give them
1037
931
            # an empty tuple instead.
1038
932
            parents = ()
1039
 
        lines = content_factory.get_bytes_as('lines')
1040
 
        line_bytes = content_factory.get_bytes_as('fulltext')
1041
 
        return self._add(key, lines, parents,
1042
 
                         parent_texts, left_matching_blocks, nostore_sha, random_id,
1043
 
                         line_bytes=line_bytes)
 
933
        return self._add(key, None, parents,
 
934
            None, None, nostore_sha, random_id,
 
935
            line_bytes=text)
1044
936
 
1045
937
    def _add(self, key, lines, parents, parent_texts,
1046
 
             left_matching_blocks, nostore_sha, random_id,
1047
 
             line_bytes):
 
938
        left_matching_blocks, nostore_sha, random_id,
 
939
        line_bytes):
1048
940
        """Add a set of lines on top of version specified by parents.
1049
941
 
1050
942
        Any versions not present will be converted into ghosts.
1063
955
        # that out.
1064
956
        digest = sha_string(line_bytes)
1065
957
        if nostore_sha == digest:
1066
 
            raise ExistingContent
 
958
            raise errors.ExistingContent
1067
959
 
1068
960
        present_parents = []
1069
961
        if parent_texts is None:
1076
968
                present_parents.append(parent)
1077
969
 
1078
970
        # Currently we can only compress against the left most present parent.
1079
 
        if (len(present_parents) == 0
1080
 
                or present_parents[0] != parents[0]):
 
971
        if (len(present_parents) == 0 or
 
972
            present_parents[0] != parents[0]):
1081
973
            delta = False
1082
974
        else:
1083
975
            # To speed the extract of texts the delta chain is limited
1091
983
        # Note: line_bytes is not modified to add a newline, that is tracked
1092
984
        #       via the no_eol flag. 'lines' *is* modified, because that is the
1093
985
        #       general values needed by the Content code.
1094
 
        if line_bytes and not line_bytes.endswith(b'\n'):
1095
 
            options.append(b'no-eol')
 
986
        if line_bytes and line_bytes[-1] != '\n':
 
987
            options.append('no-eol')
1096
988
            no_eol = True
1097
989
            # Copy the existing list, or create a new one
1098
990
            if lines is None:
1100
992
            else:
1101
993
                lines = lines[:]
1102
994
            # Replace the last line with one that ends in a final newline
1103
 
            lines[-1] = lines[-1] + b'\n'
 
995
            lines[-1] = lines[-1] + '\n'
1104
996
        if lines is None:
1105
997
            lines = osutils.split_lines(line_bytes)
1106
998
 
1107
999
        for element in key[:-1]:
1108
 
            if not isinstance(element, bytes):
1109
 
                raise TypeError("key contains non-bytestrings: %r" % (key,))
 
1000
            if type(element) is not str:
 
1001
                raise TypeError("key contains non-strings: %r" % (key,))
1110
1002
        if key[-1] is None:
1111
 
            key = key[:-1] + (b'sha1:' + digest,)
1112
 
        elif not isinstance(key[-1], bytes):
1113
 
            raise TypeError("key contains non-bytestrings: %r" % (key,))
 
1003
            key = key[:-1] + ('sha1:' + digest,)
 
1004
        elif type(key[-1]) is not str:
 
1005
                raise TypeError("key contains non-strings: %r" % (key,))
1114
1006
        # Knit hunks are still last-element only
1115
1007
        version_id = key[-1]
1116
1008
        content = self._factory.make(lines, version_id)
1121
1013
        if delta or (self._factory.annotated and len(present_parents) > 0):
1122
1014
            # Merge annotations from parent texts if needed.
1123
1015
            delta_hunks = self._merge_annotations(content, present_parents,
1124
 
                                                  parent_texts, delta, self._factory.annotated,
1125
 
                                                  left_matching_blocks)
 
1016
                parent_texts, delta, self._factory.annotated,
 
1017
                left_matching_blocks)
1126
1018
 
1127
1019
        if delta:
1128
 
            options.append(b'line-delta')
 
1020
            options.append('line-delta')
1129
1021
            store_lines = self._factory.lower_line_delta(delta_hunks)
1130
 
            size, data = self._record_to_data(key, digest, store_lines)
 
1022
            size, bytes = self._record_to_data(key, digest,
 
1023
                store_lines)
1131
1024
        else:
1132
 
            options.append(b'fulltext')
 
1025
            options.append('fulltext')
1133
1026
            # isinstance is slower and we have no hierarchy.
1134
1027
            if self._factory.__class__ is KnitPlainFactory:
1135
1028
                # Use the already joined bytes saving iteration time in
1136
1029
                # _record_to_data.
1137
1030
                dense_lines = [line_bytes]
1138
1031
                if no_eol:
1139
 
                    dense_lines.append(b'\n')
1140
 
                size, data = self._record_to_data(key, digest,
1141
 
                                                  lines, dense_lines)
 
1032
                    dense_lines.append('\n')
 
1033
                size, bytes = self._record_to_data(key, digest,
 
1034
                    lines, dense_lines)
1142
1035
            else:
1143
1036
                # get mixed annotation + content and feed it into the
1144
1037
                # serialiser.
1145
1038
                store_lines = self._factory.lower_fulltext(content)
1146
 
                size, data = self._record_to_data(key, digest, store_lines)
 
1039
                size, bytes = self._record_to_data(key, digest,
 
1040
                    store_lines)
1147
1041
 
1148
 
        access_memo = self._access.add_raw_record(key, size, data)
 
1042
        access_memo = self._access.add_raw_records([(key, size)], bytes)[0]
1149
1043
        self._index.add_records(
1150
1044
            ((key, options, access_memo, parents),),
1151
1045
            random_id=random_id)
1177
1071
            if self._index.get_method(key) != 'fulltext':
1178
1072
                compression_parent = parent_map[key][0]
1179
1073
                if compression_parent not in parent_map:
1180
 
                    raise KnitCorrupt(self,
1181
 
                                      "Missing basis parent %s for %s" % (
1182
 
                                          compression_parent, key))
 
1074
                    raise errors.KnitCorrupt(self,
 
1075
                        "Missing basis parent %s for %s" % (
 
1076
                        compression_parent, key))
1183
1077
        for fallback_vfs in self._immediate_fallback_vfs:
1184
1078
            fallback_vfs.check()
1185
1079
 
1186
1080
    def _check_add(self, key, lines, random_id, check_content):
1187
1081
        """check that version_id and lines are safe to add."""
1188
 
        if not all(isinstance(x, bytes) or x is None for x in key):
1189
 
            raise TypeError(key)
1190
1082
        version_id = key[-1]
1191
1083
        if version_id is not None:
1192
1084
            if contains_whitespace(version_id):
1212
1104
        """
1213
1105
        if rec[1] != version_id:
1214
1106
            raise KnitCorrupt(self,
1215
 
                              'unexpected version, wanted %r, got %r' % (version_id, rec[1]))
 
1107
                'unexpected version, wanted %r, got %r' % (version_id, rec[1]))
1216
1108
 
1217
1109
    def _check_should_delta(self, parent):
1218
1110
        """Iterate back through the parent listing, looking for a fulltext.
1227
1119
        """
1228
1120
        delta_size = 0
1229
1121
        fulltext_size = None
1230
 
        for count in range(self._max_delta_chain):
 
1122
        for count in xrange(self._max_delta_chain):
1231
1123
            try:
1232
1124
                # Note that this only looks in the index of this particular
1233
1125
                # KnitVersionedFiles, not in the fallbacks.  This ensures that
1235
1127
                # boundaries.
1236
1128
                build_details = self._index.get_build_details([parent])
1237
1129
                parent_details = build_details[parent]
1238
 
            except (RevisionNotPresent, KeyError) as e:
 
1130
            except (RevisionNotPresent, KeyError), e:
1239
1131
                # Some basis is not locally present: always fulltext
1240
1132
                return False
1241
1133
            index_memo, compression_parent, _, _ = parent_details
1282
1174
            build_details = self._index.get_build_details(pending_components)
1283
1175
            current_components = set(pending_components)
1284
1176
            pending_components = set()
1285
 
            for key, details in build_details.items():
 
1177
            for key, details in build_details.iteritems():
1286
1178
                (index_memo, compression_parent, parents,
1287
1179
                 record_details) = details
 
1180
                method = record_details[0]
1288
1181
                if compression_parent is not None:
1289
1182
                    pending_components.add(compression_parent)
1290
 
                component_data[key] = self._build_details_to_components(
1291
 
                    details)
 
1183
                component_data[key] = self._build_details_to_components(details)
1292
1184
            missing = current_components.difference(build_details)
1293
1185
            if missing and not allow_missing:
1294
1186
                raise errors.RevisionNotPresent(missing.pop(), self)
1354
1246
            error, just return the data that could be generated.
1355
1247
        """
1356
1248
        raw_map = self._get_record_map_unparsed(keys,
1357
 
                                                allow_missing=allow_missing)
 
1249
            allow_missing=allow_missing)
1358
1250
        return self._raw_map_to_record_map(raw_map)
1359
1251
 
1360
1252
    def _raw_map_to_record_map(self, raw_map):
1385
1277
        while True:
1386
1278
            try:
1387
1279
                position_map = self._get_components_positions(keys,
1388
 
                                                              allow_missing=allow_missing)
 
1280
                    allow_missing=allow_missing)
1389
1281
                # key = component_id, r = record_details, i_m = index_memo,
1390
1282
                # n = next
1391
1283
                records = [(key, i_m) for key, (r, i_m, n)
1392
 
                           in position_map.items()]
 
1284
                                       in position_map.iteritems()]
1393
1285
                # Sort by the index memo, so that we request records from the
1394
1286
                # same pack file together, and in forward-sorted order
1395
1287
                records.sort(key=operator.itemgetter(1))
1398
1290
                    (record_details, index_memo, next) = position_map[key]
1399
1291
                    raw_record_map[key] = data, record_details, next
1400
1292
                return raw_record_map
1401
 
            except errors.RetryWithNewPacks as e:
 
1293
            except errors.RetryWithNewPacks, e:
1402
1294
                self._access.reload_or_raise(e)
1403
1295
 
1404
1296
    @classmethod
1424
1316
        prefix_order = []
1425
1317
        for key in keys:
1426
1318
            if len(key) == 1:
1427
 
                prefix = b''
 
1319
                prefix = ''
1428
1320
            else:
1429
1321
                prefix = key[0]
1430
1322
 
1503
1395
            try:
1504
1396
                keys = set(remaining_keys)
1505
1397
                for content_factory in self._get_remaining_record_stream(keys,
1506
 
                                                                         ordering, include_delta_closure):
 
1398
                                            ordering, include_delta_closure):
1507
1399
                    remaining_keys.discard(content_factory.key)
1508
1400
                    yield content_factory
1509
1401
                return
1510
 
            except errors.RetryWithNewPacks as e:
 
1402
            except errors.RetryWithNewPacks, e:
1511
1403
                self._access.reload_or_raise(e)
1512
1404
 
1513
1405
    def _get_remaining_record_stream(self, keys, ordering,
1514
1406
                                     include_delta_closure):
1515
1407
        """This function is the 'retry' portion for get_record_stream."""
1516
1408
        if include_delta_closure:
1517
 
            positions = self._get_components_positions(
1518
 
                keys, allow_missing=True)
 
1409
            positions = self._get_components_positions(keys, allow_missing=True)
1519
1410
        else:
1520
1411
            build_details = self._index.get_build_details(keys)
1521
1412
            # map from key to
1522
1413
            # (record_details, access_memo, compression_parent_key)
1523
1414
            positions = dict((key, self._build_details_to_components(details))
1524
 
                             for key, details in build_details.items())
 
1415
                for key, details in build_details.iteritems())
1525
1416
        absent_keys = keys.difference(set(positions))
1526
1417
        # There may be more absent keys : if we're missing the basis component
1527
1418
        # and are trying to include the delta closure.
1579
1470
        else:
1580
1471
            if ordering != 'unordered':
1581
1472
                raise AssertionError('valid values for ordering are:'
1582
 
                                     ' "unordered", "groupcompress" or "topological" not: %r'
1583
 
                                     % (ordering,))
 
1473
                    ' "unordered", "groupcompress" or "topological" not: %r'
 
1474
                    % (ordering,))
1584
1475
            # Just group by source; remote sources first.
1585
1476
            present_keys = []
1586
1477
            source_keys = []
1623
1514
                    for key, raw_data in self._read_records_iter_unchecked(records):
1624
1515
                        (record_details, index_memo, _) = positions[key]
1625
1516
                        yield KnitContentFactory(key, global_map[key],
1626
 
                                                 record_details, None, raw_data, self._factory.annotated, None)
 
1517
                            record_details, None, raw_data, self._factory.annotated, None)
1627
1518
                else:
1628
 
                    vf = self._immediate_fallback_vfs[parent_maps.index(
1629
 
                        source) - 1]
 
1519
                    vf = self._immediate_fallback_vfs[parent_maps.index(source) - 1]
1630
1520
                    for record in vf.get_record_stream(keys, ordering,
1631
 
                                                       include_delta_closure):
 
1521
                        include_delta_closure):
1632
1522
                        yield record
1633
1523
 
1634
1524
    def get_sha1s(self, keys):
1636
1526
        missing = set(keys)
1637
1527
        record_map = self._get_record_map(missing, allow_missing=True)
1638
1528
        result = {}
1639
 
        for key, details in record_map.items():
 
1529
        for key, details in record_map.iteritems():
1640
1530
            if key not in missing:
1641
1531
                continue
1642
1532
            # record entry 2 is the 'digest'.
1673
1563
        else:
1674
1564
            # self is not annotated, but we can strip annotations cheaply.
1675
1565
            annotated = ""
1676
 
            convertibles = {"knit-annotated-ft-gz"}
 
1566
            convertibles = set(["knit-annotated-ft-gz"])
1677
1567
            if self._max_delta_chain:
1678
1568
                delta_types.add("knit-annotated-delta-gz")
1679
1569
                convertibles.add("knit-annotated-delta-gz")
1717
1607
            # Raise an error when a record is missing.
1718
1608
            if record.storage_kind == 'absent':
1719
1609
                raise RevisionNotPresent([record.key], self)
1720
 
            elif ((record.storage_kind in knit_types) and
1721
 
                  (compression_parent is None or
1722
 
                   not self._immediate_fallback_vfs or
1723
 
                   compression_parent in self._index or
1724
 
                   compression_parent not in self)):
 
1610
            elif ((record.storage_kind in knit_types)
 
1611
                  and (compression_parent is None
 
1612
                       or not self._immediate_fallback_vfs
 
1613
                       or self._index.has_key(compression_parent)
 
1614
                       or not self.has_key(compression_parent))):
1725
1615
                # we can insert the knit record literally if either it has no
1726
1616
                # compression parent OR we already have its basis in this kvf
1727
1617
                # OR the basis is not present even in the fallbacks.  In the
1729
1619
                # will be well, or it won't turn up at all and we'll raise an
1730
1620
                # error at the end.
1731
1621
                #
1732
 
                # TODO: self.__contains__ is somewhat redundant with
1733
 
                # self._index.__contains__; we really want something that directly
 
1622
                # TODO: self.has_key is somewhat redundant with
 
1623
                # self._index.has_key; we really want something that directly
1734
1624
                # asks if it's only present in the fallbacks. -- mbp 20081119
1735
1625
                if record.storage_kind not in native_types:
1736
1626
                    try:
1739
1629
                    except KeyError:
1740
1630
                        adapter_key = (record.storage_kind, "knit-ft-gz")
1741
1631
                        adapter = get_adapter(adapter_key)
1742
 
                    bytes = adapter.get_bytes(record, adapter_key[1])
 
1632
                    bytes = adapter.get_bytes(record)
1743
1633
                else:
1744
1634
                    # It's a knit record, it has a _raw_record field (even if
1745
1635
                    # it was reconstituted from a network stream).
1746
1636
                    bytes = record._raw_record
1747
 
                options = [record._build_details[0].encode('ascii')]
 
1637
                options = [record._build_details[0]]
1748
1638
                if record._build_details[1]:
1749
 
                    options.append(b'no-eol')
 
1639
                    options.append('no-eol')
1750
1640
                # Just blat it across.
1751
1641
                # Note: This does end up adding data on duplicate keys. As
1752
1642
                # modern repositories use atomic insertions this should not
1755
1645
                # deprecated format this is tolerable. It can be fixed if
1756
1646
                # needed by in the kndx index support raising on a duplicate
1757
1647
                # add with identical parents and options.
1758
 
                access_memo = self._access.add_raw_record(
1759
 
                    record.key, len(bytes), [bytes])
 
1648
                access_memo = self._access.add_raw_records(
 
1649
                    [(record.key, len(bytes))], bytes)[0]
1760
1650
                index_entry = (record.key, options, access_memo, parents)
1761
 
                if b'fulltext' not in options:
 
1651
                if 'fulltext' not in options:
1762
1652
                    # Not a fulltext, so we need to make sure the compression
1763
1653
                    # parent will also be present.
1764
1654
                    # Note that pack backed knits don't need to buffer here
1769
1659
                    #
1770
1660
                    # They're required to be physically in this
1771
1661
                    # KnitVersionedFiles, not in a fallback.
1772
 
                    if compression_parent not in self._index:
 
1662
                    if not self._index.has_key(compression_parent):
1773
1663
                        pending = buffered_index_entries.setdefault(
1774
1664
                            compression_parent, [])
1775
1665
                        pending.append(index_entry)
1776
1666
                        buffered = True
1777
1667
                if not buffered:
1778
1668
                    self._index.add_records([index_entry])
1779
 
            elif record.storage_kind in ('chunked', 'file'):
1780
 
                self.add_lines(record.key, parents, record.get_bytes_as('lines'))
 
1669
            elif record.storage_kind == 'chunked':
 
1670
                self.add_lines(record.key, parents,
 
1671
                    osutils.chunks_to_lines(record.get_bytes_as('chunked')))
1781
1672
            else:
1782
1673
                # Not suitable for direct insertion as a
1783
1674
                # delta, either because it's not the right format, or this
1787
1678
                self._access.flush()
1788
1679
                try:
1789
1680
                    # Try getting a fulltext directly from the record.
1790
 
                    lines = record.get_bytes_as('lines')
1791
 
                except UnavailableRepresentation:
1792
 
                    adapter_key = record.storage_kind, 'lines'
 
1681
                    bytes = record.get_bytes_as('fulltext')
 
1682
                except errors.UnavailableRepresentation:
 
1683
                    adapter_key = record.storage_kind, 'fulltext'
1793
1684
                    adapter = get_adapter(adapter_key)
1794
 
                    lines = adapter.get_bytes(record, 'lines')
 
1685
                    bytes = adapter.get_bytes(record)
 
1686
                lines = split_lines(bytes)
1795
1687
                try:
1796
1688
                    self.add_lines(record.key, parents, lines)
1797
1689
                except errors.RevisionAlreadyPresent:
1866
1758
                # we need key, position, length
1867
1759
                key_records = []
1868
1760
                build_details = self._index.get_build_details(keys)
1869
 
                for key, details in build_details.items():
 
1761
                for key, details in build_details.iteritems():
1870
1762
                    if key in keys:
1871
1763
                        key_records.append((key, details[0]))
1872
1764
                records_iter = enumerate(self._read_records_iter(key_records))
1875
1767
                    compression_parent = build_details[key][1]
1876
1768
                    if compression_parent is None:
1877
1769
                        # fulltext
1878
 
                        line_iterator = self._factory.get_fulltext_content(
1879
 
                            data)
 
1770
                        line_iterator = self._factory.get_fulltext_content(data)
1880
1771
                    else:
1881
1772
                        # Delta
1882
 
                        line_iterator = self._factory.get_linedelta_content(
1883
 
                            data)
 
1773
                        line_iterator = self._factory.get_linedelta_content(data)
1884
1774
                    # Now that we are yielding the data for this key, remove it
1885
1775
                    # from the list
1886
1776
                    keys.remove(key)
1891
1781
                    for line in line_iterator:
1892
1782
                        yield line, key
1893
1783
                done = True
1894
 
            except errors.RetryWithNewPacks as e:
 
1784
            except errors.RetryWithNewPacks, e:
1895
1785
                self._access.reload_or_raise(e)
1896
1786
        # If there are still keys we've not yet found, we look in the fallback
1897
1787
        # vfs, and hope to find them there.  Note that if the keys are found
1917
1807
        for op in delta_seq.get_opcodes():
1918
1808
            if op[0] == 'equal':
1919
1809
                continue
1920
 
            diff_hunks.append(
1921
 
                (op[1], op[2], op[4] - op[3], new_content._lines[op[3]:op[4]]))
 
1810
            diff_hunks.append((op[1], op[2], op[4]-op[3], new_content._lines[op[3]:op[4]]))
1922
1811
        return diff_hunks
1923
1812
 
1924
1813
    def _merge_annotations(self, content, parents, parent_texts={},
1948
1837
                    # this copies (origin, text) pairs across to the new
1949
1838
                    # content for any line that matches the last-checked
1950
1839
                    # parent.
1951
 
                    content._lines[j:j + n] = merge_content._lines[i:i + n]
 
1840
                    content._lines[j:j+n] = merge_content._lines[i:i+n]
1952
1841
            # XXX: Robert says the following block is a workaround for a
1953
1842
            # now-fixed bug and it can probably be deleted. -- mbp 20080618
1954
 
            if content._lines and not content._lines[-1][1].endswith(b'\n'):
 
1843
            if content._lines and content._lines[-1][1][-1] != '\n':
1955
1844
                # The copied annotation was from a line without a trailing EOL,
1956
1845
                # reinstate one for the content object, to ensure correct
1957
1846
                # serialization.
1958
 
                line = content._lines[-1][1] + b'\n'
 
1847
                line = content._lines[-1][1] + '\n'
1959
1848
                content._lines[-1] = (content._lines[-1][0], line)
1960
1849
        if delta:
1961
1850
            if delta_seq is None:
1963
1852
                new_texts = content.text()
1964
1853
                old_texts = reference_content.text()
1965
1854
                delta_seq = patiencediff.PatienceSequenceMatcher(
1966
 
                    None, old_texts, new_texts)
 
1855
                                                 None, old_texts, new_texts)
1967
1856
            return self._make_line_delta(delta_seq, content)
1968
1857
 
1969
1858
    def _parse_record(self, version_id, data):
1981
1870
        :return: the header and the decompressor stream.
1982
1871
                 as (stream, header_record)
1983
1872
        """
1984
 
        df = gzip.GzipFile(mode='rb', fileobj=BytesIO(raw_data))
 
1873
        df = gzip.GzipFile(mode='rb', fileobj=StringIO(raw_data))
1985
1874
        try:
1986
1875
            # Current serialise
1987
1876
            rec = self._check_header(key, df.readline())
1988
 
        except Exception as e:
 
1877
        except Exception, e:
1989
1878
            raise KnitCorrupt(self,
1990
1879
                              "While reading {%s} got %s(%s)"
1991
1880
                              % (key, e.__class__.__name__, str(e)))
1996
1885
        # 4168 calls in 2880 217 internal
1997
1886
        # 4168 calls to _parse_record_header in 2121
1998
1887
        # 4168 calls to readlines in 330
1999
 
        with gzip.GzipFile(mode='rb', fileobj=BytesIO(data)) as df:
2000
 
            try:
2001
 
                record_contents = df.readlines()
2002
 
            except Exception as e:
2003
 
                raise KnitCorrupt(self, "Corrupt compressed record %r, got %s(%s)" %
2004
 
                                  (data, e.__class__.__name__, str(e)))
2005
 
            header = record_contents.pop(0)
2006
 
            rec = self._split_header(header)
2007
 
            last_line = record_contents.pop()
2008
 
            if len(record_contents) != int(rec[2]):
2009
 
                raise KnitCorrupt(self,
2010
 
                                  'incorrect number of lines %s != %s'
2011
 
                                  ' for version {%s} %s'
2012
 
                                  % (len(record_contents), int(rec[2]),
2013
 
                                     rec[1], record_contents))
2014
 
            if last_line != b'end %s\n' % rec[1]:
2015
 
                raise KnitCorrupt(self,
2016
 
                                  'unexpected version end line %r, wanted %r'
2017
 
                                  % (last_line, rec[1]))
 
1888
        df = gzip.GzipFile(mode='rb', fileobj=StringIO(data))
 
1889
        try:
 
1890
            record_contents = df.readlines()
 
1891
        except Exception, e:
 
1892
            raise KnitCorrupt(self, "Corrupt compressed record %r, got %s(%s)" %
 
1893
                (data, e.__class__.__name__, str(e)))
 
1894
        header = record_contents.pop(0)
 
1895
        rec = self._split_header(header)
 
1896
        last_line = record_contents.pop()
 
1897
        if len(record_contents) != int(rec[2]):
 
1898
            raise KnitCorrupt(self,
 
1899
                              'incorrect number of lines %s != %s'
 
1900
                              ' for version {%s} %s'
 
1901
                              % (len(record_contents), int(rec[2]),
 
1902
                                 rec[1], record_contents))
 
1903
        if last_line != 'end %s\n' % rec[1]:
 
1904
            raise KnitCorrupt(self,
 
1905
                              'unexpected version end line %r, wanted %r'
 
1906
                              % (last_line, rec[1]))
 
1907
        df.close()
2018
1908
        return rec, record_contents
2019
1909
 
2020
1910
    def _read_records_iter(self, records):
2041
1931
        raw_data = self._access.get_raw_records(
2042
1932
            [index_memo for key, index_memo in needed_records])
2043
1933
 
2044
 
        for (key, index_memo), data in zip(needed_records, raw_data):
 
1934
        for (key, index_memo), data in \
 
1935
                izip(iter(needed_records), raw_data):
2045
1936
            content, digest = self._parse_record(key[-1], data)
2046
1937
            yield key, content, digest
2047
1938
 
2073
1964
        if len(records):
2074
1965
            # grab the disk data needed.
2075
1966
            needed_offsets = [index_memo for key, index_memo
2076
 
                              in records]
 
1967
                                           in records]
2077
1968
            raw_records = self._access.get_raw_records(needed_offsets)
2078
1969
 
2079
1970
        for key, index_memo in records:
2080
 
            data = next(raw_records)
 
1971
            data = raw_records.next()
2081
1972
            yield key, data
2082
1973
 
2083
1974
    def _record_to_data(self, key, digest, lines, dense_lines=None):
2091
1982
            the 1000's lines and their \\n's. Using dense_lines if it is
2092
1983
            already known is a win because the string join to create bytes in
2093
1984
            this function spends less time resizing the final string.
2094
 
        :return: (len, chunked bytestring with compressed data)
 
1985
        :return: (len, a StringIO instance with the raw data ready to read.)
2095
1986
        """
2096
 
        chunks = [b"version %s %d %s\n" % (key[-1], len(lines), digest)]
 
1987
        chunks = ["version %s %d %s\n" % (key[-1], len(lines), digest)]
2097
1988
        chunks.extend(dense_lines or lines)
2098
 
        chunks.append(b"end " + key[-1] + b"\n")
 
1989
        chunks.append("end %s\n" % key[-1])
2099
1990
        for chunk in chunks:
2100
 
            if not isinstance(chunk, bytes):
 
1991
            if type(chunk) is not str:
2101
1992
                raise AssertionError(
2102
1993
                    'data must be plain bytes was %s' % type(chunk))
2103
 
        if lines and not lines[-1].endswith(b'\n'):
 
1994
        if lines and lines[-1][-1] != '\n':
2104
1995
            raise ValueError('corrupt lines value %r' % lines)
2105
 
        compressed_chunks = tuned_gzip.chunks_to_gzip(chunks)
2106
 
        return sum(map(len, compressed_chunks)), compressed_chunks
 
1996
        compressed_bytes = tuned_gzip.chunks_to_gzip(chunks)
 
1997
        return len(compressed_bytes), compressed_bytes
2107
1998
 
2108
1999
    def _split_header(self, line):
2109
2000
        rec = line.split()
2134
2025
        # Note that _get_content is only called when the _ContentMapGenerator
2135
2026
        # has been constructed with just one key requested for reconstruction.
2136
2027
        if key in self.nonlocal_keys:
2137
 
            record = next(self.get_record_stream())
 
2028
            record = self.get_record_stream().next()
2138
2029
            # Create a content object on the fly
2139
 
            lines = record.get_bytes_as('lines')
 
2030
            lines = osutils.chunks_to_lines(record.get_bytes_as('chunked'))
2140
2031
            return PlainKnitContent(lines, record.key)
2141
2032
        else:
2142
2033
            # local keys we can ask for directly
2168
2059
            # Loop over fallback repositories asking them for texts - ignore
2169
2060
            # any missing from a particular fallback.
2170
2061
            for record in source.get_record_stream(missing_keys,
2171
 
                                                   self._ordering, True):
 
2062
                self._ordering, True):
2172
2063
                if record.storage_kind == 'absent':
2173
2064
                    # Not in thie particular stream, may be in one of the
2174
2065
                    # other fallback vfs objects.
2227
2118
                if component_id in self._contents_map:
2228
2119
                    content = self._contents_map[component_id]
2229
2120
                else:
2230
 
                    content, delta = self._factory.parse_record(
2231
 
                        key[-1], record, record_details, content,
 
2121
                    content, delta = self._factory.parse_record(key[-1],
 
2122
                        record, record_details, content,
2232
2123
                        copy_base_content=multiple_versions)
2233
2124
                    if multiple_versions:
2234
2125
                        self._contents_map[component_id] = content
2257
2148
        """
2258
2149
        lines = []
2259
2150
        # kind marker for dispatch on the far side,
2260
 
        lines.append(b'knit-delta-closure')
 
2151
        lines.append('knit-delta-closure')
2261
2152
        # Annotated or not
2262
2153
        if self.vf._factory.annotated:
2263
 
            lines.append(b'annotated')
 
2154
            lines.append('annotated')
2264
2155
        else:
2265
 
            lines.append(b'')
 
2156
            lines.append('')
2266
2157
        # then the list of keys
2267
 
        lines.append(b'\t'.join(b'\x00'.join(key) for key in self.keys
2268
 
                                if key not in self.nonlocal_keys))
 
2158
        lines.append('\t'.join(['\x00'.join(key) for key in self.keys
 
2159
            if key not in self.nonlocal_keys]))
2269
2160
        # then the _raw_record_map in serialised form:
2270
2161
        map_byte_list = []
2271
2162
        # for each item in the map:
2276
2167
        # one line with next ('' for None)
2277
2168
        # one line with byte count of the record bytes
2278
2169
        # the record bytes
2279
 
        for key, (record_bytes, (method, noeol), next) in (
2280
 
                self._raw_record_map.items()):
2281
 
            key_bytes = b'\x00'.join(key)
 
2170
        for key, (record_bytes, (method, noeol), next) in \
 
2171
            self._raw_record_map.iteritems():
 
2172
            key_bytes = '\x00'.join(key)
2282
2173
            parents = self.global_map.get(key, None)
2283
2174
            if parents is None:
2284
 
                parent_bytes = b'None:'
 
2175
                parent_bytes = 'None:'
2285
2176
            else:
2286
 
                parent_bytes = b'\t'.join(b'\x00'.join(key) for key in parents)
2287
 
            method_bytes = method.encode('ascii')
 
2177
                parent_bytes = '\t'.join('\x00'.join(key) for key in parents)
 
2178
            method_bytes = method
2288
2179
            if noeol:
2289
 
                noeol_bytes = b"T"
 
2180
                noeol_bytes = "T"
2290
2181
            else:
2291
 
                noeol_bytes = b"F"
 
2182
                noeol_bytes = "F"
2292
2183
            if next:
2293
 
                next_bytes = b'\x00'.join(next)
 
2184
                next_bytes = '\x00'.join(next)
2294
2185
            else:
2295
 
                next_bytes = b''
2296
 
            map_byte_list.append(b'\n'.join(
2297
 
                [key_bytes, parent_bytes, method_bytes, noeol_bytes, next_bytes,
2298
 
                 b'%d' % len(record_bytes), record_bytes]))
2299
 
        map_bytes = b''.join(map_byte_list)
 
2186
                next_bytes = ''
 
2187
            map_byte_list.append('%s\n%s\n%s\n%s\n%s\n%d\n%s' % (
 
2188
                key_bytes, parent_bytes, method_bytes, noeol_bytes, next_bytes,
 
2189
                len(record_bytes), record_bytes))
 
2190
        map_bytes = ''.join(map_byte_list)
2300
2191
        lines.append(map_bytes)
2301
 
        bytes = b'\n'.join(lines)
 
2192
        bytes = '\n'.join(lines)
2302
2193
        return bytes
2303
2194
 
2304
2195
 
2306
2197
    """Content map generator reading from a VersionedFiles object."""
2307
2198
 
2308
2199
    def __init__(self, versioned_files, keys, nonlocal_keys=None,
2309
 
                 global_map=None, raw_record_map=None, ordering='unordered'):
 
2200
        global_map=None, raw_record_map=None, ordering='unordered'):
2310
2201
        """Create a _ContentMapGenerator.
2311
2202
 
2312
2203
        :param versioned_files: The versioned files that the texts are being
2341
2232
        self._record_map = None
2342
2233
        if raw_record_map is None:
2343
2234
            self._raw_record_map = self.vf._get_record_map_unparsed(keys,
2344
 
                                                                    allow_missing=True)
 
2235
                allow_missing=True)
2345
2236
        else:
2346
2237
            self._raw_record_map = raw_record_map
2347
2238
        # the factory for parsing records
2363
2254
        self.vf = KnitVersionedFiles(None, None)
2364
2255
        start = line_end
2365
2256
        # Annotated or not
2366
 
        line_end = bytes.find(b'\n', start)
 
2257
        line_end = bytes.find('\n', start)
2367
2258
        line = bytes[start:line_end]
2368
2259
        start = line_end + 1
2369
 
        if line == b'annotated':
 
2260
        if line == 'annotated':
2370
2261
            self._factory = KnitAnnotateFactory()
2371
2262
        else:
2372
2263
            self._factory = KnitPlainFactory()
2373
2264
        # list of keys to emit in get_record_stream
2374
 
        line_end = bytes.find(b'\n', start)
 
2265
        line_end = bytes.find('\n', start)
2375
2266
        line = bytes[start:line_end]
2376
2267
        start = line_end + 1
2377
2268
        self.keys = [
2378
 
            tuple(segment.split(b'\x00')) for segment in line.split(b'\t')
 
2269
            tuple(segment.split('\x00')) for segment in line.split('\t')
2379
2270
            if segment]
2380
2271
        # now a loop until the end. XXX: It would be nice if this was just a
2381
2272
        # bunch of the same records as get_record_stream(..., False) gives, but
2383
2274
        end = len(bytes)
2384
2275
        while start < end:
2385
2276
            # 1 line with key
2386
 
            line_end = bytes.find(b'\n', start)
2387
 
            key = tuple(bytes[start:line_end].split(b'\x00'))
 
2277
            line_end = bytes.find('\n', start)
 
2278
            key = tuple(bytes[start:line_end].split('\x00'))
2388
2279
            start = line_end + 1
2389
2280
            # 1 line with parents (None: for None, '' for ())
2390
 
            line_end = bytes.find(b'\n', start)
 
2281
            line_end = bytes.find('\n', start)
2391
2282
            line = bytes[start:line_end]
2392
 
            if line == b'None:':
 
2283
            if line == 'None:':
2393
2284
                parents = None
2394
2285
            else:
2395
2286
                parents = tuple(
2396
 
                    tuple(segment.split(b'\x00')) for segment in line.split(b'\t')
2397
 
                    if segment)
 
2287
                    [tuple(segment.split('\x00')) for segment in line.split('\t')
 
2288
                     if segment])
2398
2289
            self.global_map[key] = parents
2399
2290
            start = line_end + 1
2400
2291
            # one line with method
2401
 
            line_end = bytes.find(b'\n', start)
 
2292
            line_end = bytes.find('\n', start)
2402
2293
            line = bytes[start:line_end]
2403
 
            method = line.decode('ascii')
 
2294
            method = line
2404
2295
            start = line_end + 1
2405
2296
            # one line with noeol
2406
 
            line_end = bytes.find(b'\n', start)
 
2297
            line_end = bytes.find('\n', start)
2407
2298
            line = bytes[start:line_end]
2408
 
            noeol = line == b"T"
 
2299
            noeol = line == "T"
2409
2300
            start = line_end + 1
2410
 
            # one line with next (b'' for None)
2411
 
            line_end = bytes.find(b'\n', start)
 
2301
            # one line with next ('' for None)
 
2302
            line_end = bytes.find('\n', start)
2412
2303
            line = bytes[start:line_end]
2413
2304
            if not line:
2414
2305
                next = None
2415
2306
            else:
2416
 
                next = tuple(bytes[start:line_end].split(b'\x00'))
 
2307
                next = tuple(bytes[start:line_end].split('\x00'))
2417
2308
            start = line_end + 1
2418
2309
            # one line with byte count of the record bytes
2419
 
            line_end = bytes.find(b'\n', start)
 
2310
            line_end = bytes.find('\n', start)
2420
2311
            line = bytes[start:line_end]
2421
2312
            count = int(line)
2422
2313
            start = line_end + 1
2423
2314
            # the record bytes
2424
 
            record_bytes = bytes[start:start + count]
 
2315
            record_bytes = bytes[start:start+count]
2425
2316
            start = start + count
2426
2317
            # put it in the map
2427
2318
            self._raw_record_map[key] = (record_bytes, (method, noeol), next)
2496
2387
        ABI change with the C extension that reads .kndx files.
2497
2388
    """
2498
2389
 
2499
 
    HEADER = b"# bzr knit index 8\n"
 
2390
    HEADER = "# bzr knit index 8\n"
2500
2391
 
2501
2392
    def __init__(self, transport, mapper, get_scope, allow_writes, is_locked):
2502
2393
        """Create a _KndxIndex on transport using mapper."""
2541
2432
 
2542
2433
            try:
2543
2434
                for key, options, (_, pos, size), parents in path_keys:
2544
 
                    if not all(isinstance(option, bytes) for option in options):
2545
 
                        raise TypeError(options)
2546
2435
                    if parents is None:
2547
2436
                        # kndx indices cannot be parentless.
2548
2437
                        parents = ()
2549
 
                    line = b' '.join([
2550
 
                        b'\n'
2551
 
                        + key[-1], b','.join(options), b'%d' % pos, b'%d' % size,
2552
 
                        self._dictionary_compress(parents), b':'])
2553
 
                    if not isinstance(line, bytes):
 
2438
                    line = "\n%s %s %s %s %s :" % (
 
2439
                        key[-1], ','.join(options), pos, size,
 
2440
                        self._dictionary_compress(parents))
 
2441
                    if type(line) is not str:
2554
2442
                        raise AssertionError(
2555
2443
                            'data must be utf8 was %s' % type(line))
2556
2444
                    lines.append(line)
2557
2445
                    self._cache_key(key, options, pos, size, parents)
2558
2446
                if len(orig_history):
2559
 
                    self._transport.append_bytes(path, b''.join(lines))
 
2447
                    self._transport.append_bytes(path, ''.join(lines))
2560
2448
                else:
2561
2449
                    self._init_index(path, lines)
2562
2450
            except:
2600
2488
        else:
2601
2489
            index = cache[version_id][5]
2602
2490
        cache[version_id] = (version_id,
2603
 
                             options,
2604
 
                             pos,
2605
 
                             size,
2606
 
                             parents,
2607
 
                             index)
 
2491
                                   options,
 
2492
                                   pos,
 
2493
                                   size,
 
2494
                                   parents,
 
2495
                                   index)
2608
2496
 
2609
2497
    def check_header(self, fp):
2610
2498
        line = fp.readline()
2611
 
        if line == b'':
 
2499
        if line == '':
2612
2500
            # An empty file can actually be treated as though the file doesn't
2613
2501
            # exist yet.
2614
2502
            raise errors.NoSuchFile(self)
2653
2541
        result = {}
2654
2542
        for key in keys:
2655
2543
            if key not in parent_map:
2656
 
                continue  # Ghost
 
2544
                continue # Ghost
2657
2545
            method = self.get_method(key)
2658
 
            if not isinstance(method, str):
2659
 
                raise TypeError(method)
2660
2546
            parents = parent_map[key]
2661
2547
            if method == 'fulltext':
2662
2548
                compression_parent = None
2663
2549
            else:
2664
2550
                compression_parent = parents[0]
2665
 
            noeol = b'no-eol' in self.get_options(key)
 
2551
            noeol = 'no-eol' in self.get_options(key)
2666
2552
            index_memo = self.get_position(key)
2667
2553
            result[key] = (index_memo, compression_parent,
2668
 
                           parents, (method, noeol))
 
2554
                                  parents, (method, noeol))
2669
2555
        return result
2670
2556
 
2671
2557
    def get_method(self, key):
2672
2558
        """Return compression method of specified key."""
2673
2559
        options = self.get_options(key)
2674
 
        if b'fulltext' in options:
 
2560
        if 'fulltext' in options:
2675
2561
            return 'fulltext'
2676
 
        elif b'line-delta' in options:
 
2562
        elif 'line-delta' in options:
2677
2563
            return 'line-delta'
2678
2564
        else:
2679
 
            raise KnitIndexUnknownMethod(self, options)
 
2565
            raise errors.KnitIndexUnknownMethod(self, options)
2680
2566
 
2681
2567
    def get_options(self, key):
2682
2568
        """Return a list representing options.
2714
2600
                                     for suffix in suffix_parents])
2715
2601
                parent_map[key] = parent_keys
2716
2602
                pending_keys.extend([p for p in parent_keys
2717
 
                                     if p not in parent_map])
 
2603
                                        if p not in parent_map])
2718
2604
        return parent_map, missing_keys
2719
2605
 
2720
2606
    def get_parent_map(self, keys):
2738
2624
                pass
2739
2625
            else:
2740
2626
                result[key] = tuple(prefix + (suffix,) for
2741
 
                                    suffix in suffix_parents)
 
2627
                    suffix in suffix_parents)
2742
2628
        return result
2743
2629
 
2744
2630
    def get_position(self, key):
2752
2638
        entry = self._kndx_cache[prefix][0][suffix]
2753
2639
        return key, entry[2], entry[3]
2754
2640
 
2755
 
    __contains__ = _mod_index._has_key_from_parent_map
 
2641
    has_key = _mod_index._has_key_from_parent_map
2756
2642
 
2757
2643
    def _init_index(self, path, extra_lines=[]):
2758
2644
        """Initialize an index."""
2759
 
        sio = BytesIO()
 
2645
        sio = StringIO()
2760
2646
        sio.write(self.HEADER)
2761
2647
        sio.writelines(extra_lines)
2762
2648
        sio.seek(0)
2763
2649
        self._transport.put_file_non_atomic(path, sio,
2764
 
                                            create_parent_dir=True)
2765
 
        # self._create_parent_dir)
2766
 
        # mode=self._file_mode,
2767
 
        # dir_mode=self._dir_mode)
 
2650
                            create_parent_dir=True)
 
2651
                           # self._create_parent_dir)
 
2652
                           # mode=self._file_mode,
 
2653
                           # dir_mode=self._dir_mode)
2768
2654
 
2769
2655
    def keys(self):
2770
2656
        """Get all the keys in the collection.
2774
2660
        result = set()
2775
2661
        # Identify all key prefixes.
2776
2662
        # XXX: A bit hacky, needs polish.
2777
 
        if isinstance(self._mapper, ConstantMapper):
 
2663
        if type(self._mapper) is ConstantMapper:
2778
2664
            prefixes = [()]
2779
2665
        else:
2780
2666
            relpaths = set()
2799
2685
                self._filename = prefix
2800
2686
                try:
2801
2687
                    path = self._mapper.map(prefix) + '.kndx'
2802
 
                    with self._transport.get(path) as fp:
 
2688
                    fp = self._transport.get(path)
 
2689
                    try:
2803
2690
                        # _load_data may raise NoSuchFile if the target knit is
2804
2691
                        # completely empty.
2805
2692
                        _load_data(self, fp)
 
2693
                    finally:
 
2694
                        fp.close()
2806
2695
                    self._kndx_cache[prefix] = (self._cache, self._history)
2807
2696
                    del self._cache
2808
2697
                    del self._filename
2809
2698
                    del self._history
2810
2699
                except NoSuchFile:
2811
2700
                    self._kndx_cache[prefix] = ({}, [])
2812
 
                    if isinstance(self._mapper, ConstantMapper):
 
2701
                    if type(self._mapper) is ConstantMapper:
2813
2702
                        # preserve behaviour for revisions.kndx etc.
2814
2703
                        self._init_index(path)
2815
2704
                    del self._cache
2835
2724
            '.' prefix.
2836
2725
        """
2837
2726
        if not keys:
2838
 
            return b''
 
2727
            return ''
2839
2728
        result_list = []
2840
2729
        prefix = keys[0][:-1]
2841
2730
        cache = self._kndx_cache[prefix][0]
2845
2734
                raise ValueError("mismatched prefixes for %r" % keys)
2846
2735
            if key[-1] in cache:
2847
2736
                # -- inlined lookup() --
2848
 
                result_list.append(b'%d' % cache[key[-1]][5])
 
2737
                result_list.append(str(cache[key[-1]][5]))
2849
2738
                # -- end lookup () --
2850
2739
            else:
2851
 
                result_list.append(b'.' + key[-1])
2852
 
        return b' '.join(result_list)
 
2740
                result_list.append('.' + key[-1])
 
2741
        return ' '.join(result_list)
2853
2742
 
2854
2743
    def _reset_cache(self):
2855
2744
        # Possibly this should be a LRU cache. A dictionary from key_prefix to
2886
2775
 
2887
2776
    def _split_key(self, key):
2888
2777
        """Split key into a prefix and suffix."""
2889
 
        # GZ 2018-07-03: This is intentionally either a sequence or bytes?
2890
 
        if isinstance(key, bytes):
2891
 
            return key[:-1], key[-1:]
2892
2778
        return key[:-1], key[-1]
2893
2779
 
2894
2780
 
2896
2782
    """A KnitVersionedFiles index layered on GraphIndex."""
2897
2783
 
2898
2784
    def __init__(self, graph_index, is_locked, deltas=False, parents=True,
2899
 
                 add_callback=None, track_external_parent_refs=False):
 
2785
        add_callback=None, track_external_parent_refs=False):
2900
2786
        """Construct a KnitGraphIndex on a graph_index.
2901
2787
 
2902
 
        :param graph_index: An implementation of breezy.index.GraphIndex.
 
2788
        :param graph_index: An implementation of bzrlib.index.GraphIndex.
2903
2789
        :param is_locked: A callback to check whether the object should answer
2904
2790
            queries.
2905
2791
        :param deltas: Allow delta-compressed records.
2922
2808
            # XXX: TODO: Delta tree and parent graph should be conceptually
2923
2809
            # separate.
2924
2810
            raise KnitCorrupt(self, "Cannot do delta compression without "
2925
 
                              "parent tracking.")
 
2811
                "parent tracking.")
2926
2812
        self.has_graph = parents
2927
2813
        self._is_locked = is_locked
2928
2814
        self._missing_compression_parents = set()
2935
2821
        return "%s(%r)" % (self.__class__.__name__, self._graph_index)
2936
2822
 
2937
2823
    def add_records(self, records, random_id=False,
2938
 
                    missing_compression_parents=False):
 
2824
        missing_compression_parents=False):
2939
2825
        """Add multiple records to the index.
2940
2826
 
2941
2827
        This function does not insert data into the Immutable GraphIndex
2966
2852
                if key_dependencies is not None:
2967
2853
                    key_dependencies.add_references(key, parents)
2968
2854
            index, pos, size = access_memo
2969
 
            if b'no-eol' in options:
2970
 
                value = b'N'
 
2855
            if 'no-eol' in options:
 
2856
                value = 'N'
2971
2857
            else:
2972
 
                value = b' '
2973
 
            value += b"%d %d" % (pos, size)
 
2858
                value = ' '
 
2859
            value += "%d %d" % (pos, size)
2974
2860
            if not self._deltas:
2975
 
                if b'line-delta' in options:
2976
 
                    raise KnitCorrupt(
2977
 
                        self, "attempt to add line-delta in non-delta knit")
 
2861
                if 'line-delta' in options:
 
2862
                    raise KnitCorrupt(self, "attempt to add line-delta in non-delta knit")
2978
2863
            if self._parents:
2979
2864
                if self._deltas:
2980
 
                    if b'line-delta' in options:
 
2865
                    if 'line-delta' in options:
2981
2866
                        node_refs = (parents, (parents[0],))
2982
2867
                        if missing_compression_parents:
2983
2868
                            compression_parents.add(parents[0])
2988
2873
            else:
2989
2874
                if parents:
2990
2875
                    raise KnitCorrupt(self, "attempt to add node with parents "
2991
 
                                      "in parentless index.")
 
2876
                        "in parentless index.")
2992
2877
                node_refs = ()
2993
2878
            keys[key] = (value, node_refs)
2994
2879
        # check for dups
2999
2884
                # Sometimes these are passed as a list rather than a tuple
3000
2885
                passed = static_tuple.as_tuples(keys[key])
3001
2886
                passed_parents = passed[1][:1]
3002
 
                if (value[0:1] != keys[key][0][0:1]
3003
 
                        or parents != passed_parents):
 
2887
                if (value[0] != keys[key][0][0] or
 
2888
                    parents != passed_parents):
3004
2889
                    node_refs = static_tuple.as_tuples(node_refs)
3005
2890
                    raise KnitCorrupt(self, "inconsistent details in add_records"
3006
 
                                      ": %s %s" % ((value, node_refs), passed))
 
2891
                        ": %s %s" % ((value, node_refs), passed))
3007
2892
                del keys[key]
3008
2893
        result = []
3009
2894
        if self._parents:
3010
 
            for key, (value, node_refs) in keys.items():
 
2895
            for key, (value, node_refs) in keys.iteritems():
3011
2896
                result.append((key, value, node_refs))
3012
2897
        else:
3013
 
            for key, (value, node_refs) in keys.items():
 
2898
            for key, (value, node_refs) in keys.iteritems():
3014
2899
                result.append((key, value))
3015
2900
        self._add_callback(result)
3016
2901
        if missing_compression_parents:
3114
2999
                compression_parent_key = None
3115
3000
            else:
3116
3001
                compression_parent_key = self._compression_parent(entry)
3117
 
            noeol = (entry[2][0:1] == b'N')
 
3002
            noeol = (entry[2][0] == 'N')
3118
3003
            if compression_parent_key:
3119
3004
                method = 'line-delta'
3120
3005
            else:
3121
3006
                method = 'fulltext'
3122
3007
            result[key] = (self._node_to_position(entry),
3123
 
                           compression_parent_key, parents,
3124
 
                           (method, noeol))
 
3008
                                  compression_parent_key, parents,
 
3009
                                  (method, noeol))
3125
3010
        return result
3126
3011
 
3127
3012
    def _get_entries(self, keys, check_present=False):
3169
3054
        e.g. ['foo', 'bar']
3170
3055
        """
3171
3056
        node = self._get_node(key)
3172
 
        options = [self._get_method(node).encode('ascii')]
3173
 
        if node[2][0:1] == b'N':
3174
 
            options.append(b'no-eol')
 
3057
        options = [self._get_method(node)]
 
3058
        if node[2][0] == 'N':
 
3059
            options.append('no-eol')
3175
3060
        return options
3176
3061
 
3177
3062
    def find_ancestry(self, keys):
3205
3090
        node = self._get_node(key)
3206
3091
        return self._node_to_position(node)
3207
3092
 
3208
 
    __contains__ = _mod_index._has_key_from_parent_map
 
3093
    has_key = _mod_index._has_key_from_parent_map
3209
3094
 
3210
3095
    def keys(self):
3211
3096
        """Get all the keys in the collection.
3219
3104
 
3220
3105
    def _node_to_position(self, node):
3221
3106
        """Convert an index value to position details."""
3222
 
        bits = node[2][1:].split(b' ')
 
3107
        bits = node[2][1:].split(' ')
3223
3108
        return node[0], int(bits[0]), int(bits[1])
3224
3109
 
3225
3110
    def _sort_keys_by_io(self, keys, positions):
3257
3142
        self._transport = transport
3258
3143
        self._mapper = mapper
3259
3144
 
3260
 
    def add_raw_record(self, key, size, raw_data):
3261
 
        """Add raw knit bytes to a storage area.
3262
 
 
3263
 
        The data is spooled to the container writer in one bytes-record per
3264
 
        raw data item.
3265
 
 
3266
 
        :param key: The key of the raw data segment
3267
 
        :param size: The size of the raw data segment
3268
 
        :param raw_data: A chunked bytestring containing the data.
3269
 
        :return: opaque index memo to retrieve the record later.
3270
 
            For _KnitKeyAccess the memo is (key, pos, length), where the key is
3271
 
            the record key.
3272
 
        """
3273
 
        path = self._mapper.map(key)
3274
 
        try:
3275
 
            base = self._transport.append_bytes(path + '.knit', b''.join(raw_data))
3276
 
        except errors.NoSuchFile:
3277
 
            self._transport.mkdir(osutils.dirname(path))
3278
 
            base = self._transport.append_bytes(path + '.knit', b''.join(raw_data))
3279
 
        # if base == 0:
3280
 
        # chmod.
3281
 
        return (key, base, size)
3282
 
 
3283
3145
    def add_raw_records(self, key_sizes, raw_data):
3284
3146
        """Add raw knit bytes to a storage area.
3285
3147
 
3288
3150
 
3289
3151
        :param sizes: An iterable of tuples containing the key and size of each
3290
3152
            raw data segment.
3291
 
        :param raw_data: A chunked bytestring containing the data.
 
3153
        :param raw_data: A bytestring containing the data.
3292
3154
        :return: A list of memos to retrieve the record later. Each memo is an
3293
3155
            opaque index memo. For _KnitKeyAccess the memo is (key, pos,
3294
3156
            length), where the key is the record key.
3295
3157
        """
3296
 
        raw_data = b''.join(raw_data)
3297
 
        if not isinstance(raw_data, bytes):
 
3158
        if type(raw_data) is not str:
3298
3159
            raise AssertionError(
3299
3160
                'data must be plain bytes was %s' % type(raw_data))
3300
3161
        result = []
3303
3164
        # append() is relatively expensive by grouping the writes to each key
3304
3165
        # prefix.
3305
3166
        for key, size in key_sizes:
3306
 
            record_bytes = [raw_data[offset:offset + size]]
3307
 
            result.append(self.add_raw_record(key, size, record_bytes))
 
3167
            path = self._mapper.map(key)
 
3168
            try:
 
3169
                base = self._transport.append_bytes(path + '.knit',
 
3170
                    raw_data[offset:offset+size])
 
3171
            except errors.NoSuchFile:
 
3172
                self._transport.mkdir(osutils.dirname(path))
 
3173
                base = self._transport.append_bytes(path + '.knit',
 
3174
                    raw_data[offset:offset+size])
 
3175
            # if base == 0:
 
3176
            # chmod.
3308
3177
            offset += size
 
3178
            result.append((key, base, size))
3309
3179
        return result
3310
3180
 
3311
3181
    def flush(self):
3312
3182
        """Flush pending writes on this access object.
3313
 
 
 
3183
        
3314
3184
        For .knit files this is a no-op.
3315
3185
        """
3316
3186
        pass
3390
3260
            passing to read_records_iter to start reading in the raw data from
3391
3261
            the pack file.
3392
3262
        """
3393
 
        pending = {key}
 
3263
        pending = set([key])
3394
3264
        records = []
3395
3265
        ann_keys = set()
3396
3266
        self._num_needed_children[key] = 1
3401
3271
            self._all_build_details.update(build_details)
3402
3272
            # new_nodes = self._vf._index._get_entries(this_iteration)
3403
3273
            pending = set()
3404
 
            for key, details in build_details.items():
 
3274
            for key, details in build_details.iteritems():
3405
3275
                (index_memo, compression_parent, parent_keys,
3406
3276
                 record_details) = details
3407
3277
                self._parent_map[key] = parent_keys
3409
3279
                records.append((key, index_memo))
3410
3280
                # Do we actually need to check _annotated_lines?
3411
3281
                pending.update([p for p in parent_keys
3412
 
                                if p not in self._all_build_details])
 
3282
                                   if p not in self._all_build_details])
3413
3283
                if parent_keys:
3414
3284
                    for parent_key in parent_keys:
3415
3285
                        if parent_key in self._num_needed_children:
3422
3292
                    else:
3423
3293
                        self._num_compression_children[compression_parent] = 1
3424
3294
 
3425
 
            missing_versions = this_iteration.difference(build_details)
 
3295
            missing_versions = this_iteration.difference(build_details.keys())
3426
3296
            if missing_versions:
3427
3297
                for key in missing_versions:
3428
3298
                    if key in self._parent_map and key in self._text_cache:
3436
3306
                            else:
3437
3307
                                self._num_needed_children[parent_key] = 1
3438
3308
                        pending.update([p for p in parent_keys
3439
 
                                        if p not in self._all_build_details])
 
3309
                                           if p not in self._all_build_details])
3440
3310
                    else:
3441
3311
                        raise errors.RevisionNotPresent(key, self._vf)
3442
3312
        # Generally we will want to read the records in reverse order, because
3455
3325
            try:
3456
3326
                records, ann_keys = self._get_build_graph(key)
3457
3327
                for idx, (sub_key, text, num_lines) in enumerate(
3458
 
                        self._extract_texts(records)):
 
3328
                                                self._extract_texts(records)):
3459
3329
                    if pb is not None:
3460
3330
                        pb.update(gettext('annotating'), idx, len(records))
3461
3331
                    yield sub_key, text, num_lines
3462
3332
                for sub_key in ann_keys:
3463
3333
                    text = self._text_cache[sub_key]
3464
 
                    num_lines = len(text)  # bad assumption
 
3334
                    num_lines = len(text) # bad assumption
3465
3335
                    yield sub_key, text, num_lines
3466
3336
                return
3467
 
            except errors.RetryWithNewPacks as e:
 
3337
            except errors.RetryWithNewPacks, e:
3468
3338
                self._vf._access.reload_or_raise(e)
3469
3339
                # The cached build_details are no longer valid
3470
3340
                self._all_build_details.clear()
3471
3341
 
3472
3342
    def _cache_delta_blocks(self, key, compression_parent, delta, lines):
3473
3343
        parent_lines = self._text_cache[compression_parent]
3474
 
        blocks = list(KnitContent.get_line_delta_blocks(
3475
 
            delta, parent_lines, lines))
 
3344
        blocks = list(KnitContent.get_line_delta_blocks(delta, parent_lines, lines))
3476
3345
        self._matching_blocks[(key, compression_parent)] = blocks
3477
3346
 
3478
3347
    def _expand_record(self, key, parent_keys, compression_parent, record,
3531
3400
            parent_annotations = self._annotations_cache[parent_key]
3532
3401
            return parent_annotations, blocks
3533
3402
        return annotate.Annotator._get_parent_annotations_and_matches(self,
3534
 
                                                                      key, text, parent_key)
 
3403
            key, text, parent_key)
3535
3404
 
3536
3405
    def _process_pending(self, key):
3537
3406
        """The content for 'key' was just processed.
3568
3437
                # Note that if there are multiple parents, we need to wait
3569
3438
                # for all of them.
3570
3439
                self._pending_annotation.setdefault(parent_key,
3571
 
                                                    []).append((key, parent_keys))
 
3440
                    []).append((key, parent_keys))
3572
3441
                return False
3573
3442
        return True
3574
3443
 
3629
3498
                    yield key, lines, len(lines)
3630
3499
                    to_process.extend(self._process_pending(key))
3631
3500
 
3632
 
 
3633
3501
try:
3634
 
    from ._knit_load_data_pyx import _load_data_c as _load_data
3635
 
except ImportError as e:
 
3502
    from bzrlib._knit_load_data_pyx import _load_data_c as _load_data
 
3503
except ImportError, e:
3636
3504
    osutils.failed_to_load_extension(e)
3637
 
    from ._knit_load_data_py import _load_data_py as _load_data
 
3505
    from bzrlib._knit_load_data_py import _load_data_py as _load_data