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

  • Committer: Jelmer Vernooij
  • Date: 2017-05-21 12:41:27 UTC
  • mto: This revision was merged to the branch mainline in revision 6623.
  • Revision ID: jelmer@jelmer.uk-20170521124127-iv8etg0vwymyai6y
s/bzr/brz/ in apport config.

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