/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/bzr/knit.py

  • Committer: Jelmer Vernooij
  • Date: 2020-03-22 19:12:43 UTC
  • mfrom: (7490.7.6 work)
  • mto: (7490.7.7 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200322191243-yx8ils8lvfmfh7rq
Merge lp:brz/3.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
51
51
 
52
52
"""
53
53
 
 
54
from __future__ import absolute_import
54
55
 
55
 
from cStringIO import StringIO
56
 
from itertools import izip
57
56
import operator
58
57
import os
59
 
import sys
60
58
 
61
 
from bzrlib.lazy_import import lazy_import
 
59
from ..lazy_import import lazy_import
62
60
lazy_import(globals(), """
63
 
from bzrlib import (
64
 
    annotate,
 
61
import patiencediff
 
62
import gzip
 
63
 
 
64
from breezy import (
65
65
    debug,
66
66
    diff,
67
 
    graph as _mod_graph,
68
 
    index as _mod_index,
69
 
    lru_cache,
70
 
    pack,
71
 
    progress,
72
67
    static_tuple,
73
68
    trace,
74
69
    tsort,
75
70
    tuned_gzip,
76
71
    ui,
77
72
    )
 
73
from breezy.bzr import (
 
74
    index as _mod_index,
 
75
    pack,
 
76
    )
 
77
 
 
78
from breezy.bzr import pack_repo
 
79
from breezy.i18n import gettext
78
80
""")
79
 
from bzrlib import (
 
81
from .. import (
 
82
    annotate,
80
83
    errors,
81
84
    osutils,
82
 
    patiencediff,
83
85
    )
84
 
from bzrlib.errors import (
85
 
    FileExists,
 
86
from ..errors import (
 
87
    InternalBzrError,
 
88
    InvalidRevisionId,
86
89
    NoSuchFile,
87
 
    KnitError,
88
 
    InvalidRevisionId,
89
 
    KnitCorrupt,
90
 
    KnitHeaderError,
91
90
    RevisionNotPresent,
92
 
    RevisionAlreadyPresent,
93
 
    SHA1KnitCorrupt,
94
91
    )
95
 
from bzrlib.osutils import (
 
92
from ..osutils import (
96
93
    contains_whitespace,
97
 
    contains_linebreaks,
98
94
    sha_string,
99
95
    sha_strings,
100
96
    split_lines,
101
97
    )
102
 
from bzrlib.versionedfile import (
 
98
from ..sixish import (
 
99
    BytesIO,
 
100
    range,
 
101
    viewitems,
 
102
    viewvalues,
 
103
    )
 
104
from ..bzr.versionedfile import (
 
105
    _KeyRefs,
103
106
    AbsentContentFactory,
104
107
    adapter_registry,
105
108
    ConstantMapper,
106
109
    ContentFactory,
107
 
    ChunkedContentFactory,
108
110
    sort_groupcompress,
109
 
    VersionedFile,
110
 
    VersionedFiles,
 
111
    VersionedFilesWithFallbacks,
111
112
    )
112
113
 
113
114
 
126
127
 
127
128
DATA_SUFFIX = '.knit'
128
129
INDEX_SUFFIX = '.kndx'
129
 
_STREAM_MIN_BUFFER_SIZE = 5*1024*1024
 
130
_STREAM_MIN_BUFFER_SIZE = 5 * 1024 * 1024
 
131
 
 
132
 
 
133
class KnitError(InternalBzrError):
 
134
 
 
135
    _fmt = "Knit error"
 
136
 
 
137
 
 
138
class KnitCorrupt(KnitError):
 
139
 
 
140
    _fmt = "Knit %(filename)s corrupt: %(how)s"
 
141
 
 
142
    def __init__(self, filename, how):
 
143
        KnitError.__init__(self)
 
144
        self.filename = filename
 
145
        self.how = how
 
146
 
 
147
 
 
148
class SHA1KnitCorrupt(KnitCorrupt):
 
149
 
 
150
    _fmt = ("Knit %(filename)s corrupt: sha-1 of reconstructed text does not "
 
151
            "match expected sha-1. key %(key)s expected sha %(expected)s actual "
 
152
            "sha %(actual)s")
 
153
 
 
154
    def __init__(self, filename, actual, expected, key, content):
 
155
        KnitError.__init__(self)
 
156
        self.filename = filename
 
157
        self.actual = actual
 
158
        self.expected = expected
 
159
        self.key = key
 
160
        self.content = content
 
161
 
 
162
 
 
163
class KnitDataStreamIncompatible(KnitError):
 
164
    # Not raised anymore, as we can convert data streams.  In future we may
 
165
    # need it again for more exotic cases, so we're keeping it around for now.
 
166
 
 
167
    _fmt = "Cannot insert knit data stream of format \"%(stream_format)s\" into knit of format \"%(target_format)s\"."
 
168
 
 
169
    def __init__(self, stream_format, target_format):
 
170
        self.stream_format = stream_format
 
171
        self.target_format = target_format
 
172
 
 
173
 
 
174
class KnitDataStreamUnknown(KnitError):
 
175
    # Indicates a data stream we don't know how to handle.
 
176
 
 
177
    _fmt = "Cannot parse knit data stream of format \"%(stream_format)s\"."
 
178
 
 
179
    def __init__(self, stream_format):
 
180
        self.stream_format = stream_format
 
181
 
 
182
 
 
183
class KnitHeaderError(KnitError):
 
184
 
 
185
    _fmt = 'Knit header error: %(badline)r unexpected for file "%(filename)s".'
 
186
 
 
187
    def __init__(self, badline, filename):
 
188
        KnitError.__init__(self)
 
189
        self.badline = badline
 
190
        self.filename = filename
 
191
 
 
192
 
 
193
class KnitIndexUnknownMethod(KnitError):
 
194
    """Raised when we don't understand the storage method.
 
195
 
 
196
    Currently only 'fulltext' and 'line-delta' are supported.
 
197
    """
 
198
 
 
199
    _fmt = ("Knit index %(filename)s does not have a known method"
 
200
            " in options: %(options)r")
 
201
 
 
202
    def __init__(self, filename, options):
 
203
        KnitError.__init__(self)
 
204
        self.filename = filename
 
205
        self.options = options
130
206
 
131
207
 
132
208
class KnitAdapter(object):
147
223
class FTAnnotatedToUnannotated(KnitAdapter):
148
224
    """An adapter from FT annotated knits to unannotated ones."""
149
225
 
150
 
    def get_bytes(self, factory):
 
226
    def get_bytes(self, factory, target_storage_kind):
 
227
        if target_storage_kind != 'knit-ft-gz':
 
228
            raise errors.UnavailableRepresentation(
 
229
                factory.key, target_storage_kind, factory.storage_kind)
151
230
        annotated_compressed_bytes = factory._raw_record
152
231
        rec, contents = \
153
232
            self._data._parse_record_unchecked(annotated_compressed_bytes)
154
233
        content = self._annotate_factory.parse_fulltext(contents, rec[1])
155
 
        size, bytes = self._data._record_to_data((rec[1],), rec[3], content.text())
156
 
        return bytes
 
234
        size, chunks = self._data._record_to_data(
 
235
            (rec[1],), rec[3], content.text())
 
236
        return b''.join(chunks)
157
237
 
158
238
 
159
239
class DeltaAnnotatedToUnannotated(KnitAdapter):
160
240
    """An adapter for deltas from annotated to unannotated."""
161
241
 
162
 
    def get_bytes(self, factory):
 
242
    def get_bytes(self, factory, target_storage_kind):
 
243
        if target_storage_kind != 'knit-delta-gz':
 
244
            raise errors.UnavailableRepresentation(
 
245
                factory.key, target_storage_kind, factory.storage_kind)
163
246
        annotated_compressed_bytes = factory._raw_record
164
247
        rec, contents = \
165
248
            self._data._parse_record_unchecked(annotated_compressed_bytes)
166
249
        delta = self._annotate_factory.parse_line_delta(contents, rec[1],
167
 
            plain=True)
 
250
                                                        plain=True)
168
251
        contents = self._plain_factory.lower_line_delta(delta)
169
 
        size, bytes = self._data._record_to_data((rec[1],), rec[3], contents)
170
 
        return bytes
 
252
        size, chunks = self._data._record_to_data((rec[1],), rec[3], contents)
 
253
        return b''.join(chunks)
171
254
 
172
255
 
173
256
class FTAnnotatedToFullText(KnitAdapter):
174
257
    """An adapter from FT annotated knits to unannotated ones."""
175
258
 
176
 
    def get_bytes(self, factory):
 
259
    def get_bytes(self, factory, target_storage_kind):
177
260
        annotated_compressed_bytes = factory._raw_record
178
261
        rec, contents = \
179
262
            self._data._parse_record_unchecked(annotated_compressed_bytes)
180
263
        content, delta = self._annotate_factory.parse_record(factory.key[-1],
181
 
            contents, factory._build_details, None)
182
 
        return ''.join(content.text())
 
264
                                                             contents, factory._build_details, None)
 
265
        if target_storage_kind == 'fulltext':
 
266
            return b''.join(content.text())
 
267
        elif target_storage_kind in ('chunked', 'lines'):
 
268
            return content.text()
 
269
        raise errors.UnavailableRepresentation(
 
270
            factory.key, target_storage_kind, factory.storage_kind)
183
271
 
184
272
 
185
273
class DeltaAnnotatedToFullText(KnitAdapter):
186
274
    """An adapter for deltas from annotated to unannotated."""
187
275
 
188
 
    def get_bytes(self, factory):
 
276
    def get_bytes(self, factory, target_storage_kind):
189
277
        annotated_compressed_bytes = factory._raw_record
190
278
        rec, contents = \
191
279
            self._data._parse_record_unchecked(annotated_compressed_bytes)
192
280
        delta = self._annotate_factory.parse_line_delta(contents, rec[1],
193
 
            plain=True)
 
281
                                                        plain=True)
194
282
        compression_parent = factory.parents[0]
195
 
        basis_entry = self._basis_vf.get_record_stream(
196
 
            [compression_parent], 'unordered', True).next()
 
283
        basis_entry = next(self._basis_vf.get_record_stream(
 
284
            [compression_parent], 'unordered', True))
197
285
        if basis_entry.storage_kind == 'absent':
198
286
            raise errors.RevisionNotPresent(compression_parent, self._basis_vf)
199
 
        basis_chunks = basis_entry.get_bytes_as('chunked')
200
 
        basis_lines = osutils.chunks_to_lines(basis_chunks)
 
287
        basis_lines = basis_entry.get_bytes_as('lines')
201
288
        # Manually apply the delta because we have one annotated content and
202
289
        # one plain.
203
290
        basis_content = PlainKnitContent(basis_lines, compression_parent)
204
291
        basis_content.apply_delta(delta, rec[1])
205
292
        basis_content._should_strip_eol = factory._build_details[1]
206
 
        return ''.join(basis_content.text())
 
293
 
 
294
        if target_storage_kind == 'fulltext':
 
295
            return b''.join(basis_content.text())
 
296
        elif target_storage_kind in ('chunked', 'lines'):
 
297
            return basis_content.text()
 
298
        raise errors.UnavailableRepresentation(
 
299
            factory.key, target_storage_kind, factory.storage_kind)
207
300
 
208
301
 
209
302
class FTPlainToFullText(KnitAdapter):
210
303
    """An adapter from FT plain knits to unannotated ones."""
211
304
 
212
 
    def get_bytes(self, factory):
 
305
    def get_bytes(self, factory, target_storage_kind):
213
306
        compressed_bytes = factory._raw_record
214
307
        rec, contents = \
215
308
            self._data._parse_record_unchecked(compressed_bytes)
216
309
        content, delta = self._plain_factory.parse_record(factory.key[-1],
217
 
            contents, factory._build_details, None)
218
 
        return ''.join(content.text())
 
310
                                                          contents, factory._build_details, None)
 
311
        if target_storage_kind == 'fulltext':
 
312
            return b''.join(content.text())
 
313
        elif target_storage_kind in ('chunked', 'lines'):
 
314
            return content.text()
 
315
        raise errors.UnavailableRepresentation(
 
316
            factory.key, target_storage_kind, factory.storage_kind)
219
317
 
220
318
 
221
319
class DeltaPlainToFullText(KnitAdapter):
222
320
    """An adapter for deltas from annotated to unannotated."""
223
321
 
224
 
    def get_bytes(self, factory):
 
322
    def get_bytes(self, factory, target_storage_kind):
225
323
        compressed_bytes = factory._raw_record
226
324
        rec, contents = \
227
325
            self._data._parse_record_unchecked(compressed_bytes)
228
326
        delta = self._plain_factory.parse_line_delta(contents, rec[1])
229
327
        compression_parent = factory.parents[0]
230
328
        # XXX: string splitting overhead.
231
 
        basis_entry = self._basis_vf.get_record_stream(
232
 
            [compression_parent], 'unordered', True).next()
 
329
        basis_entry = next(self._basis_vf.get_record_stream(
 
330
            [compression_parent], 'unordered', True))
233
331
        if basis_entry.storage_kind == 'absent':
234
332
            raise errors.RevisionNotPresent(compression_parent, self._basis_vf)
235
 
        basis_chunks = basis_entry.get_bytes_as('chunked')
236
 
        basis_lines = osutils.chunks_to_lines(basis_chunks)
 
333
        basis_lines = basis_entry.get_bytes_as('lines')
237
334
        basis_content = PlainKnitContent(basis_lines, compression_parent)
238
335
        # Manually apply the delta because we have one annotated content and
239
336
        # one plain.
240
337
        content, _ = self._plain_factory.parse_record(rec[1], contents,
241
 
            factory._build_details, basis_content)
242
 
        return ''.join(content.text())
 
338
                                                      factory._build_details, basis_content)
 
339
        if target_storage_kind == 'fulltext':
 
340
            return b''.join(content.text())
 
341
        elif target_storage_kind in ('chunked', 'lines'):
 
342
            return content.text()
 
343
        raise errors.UnavailableRepresentation(
 
344
            factory.key, target_storage_kind, factory.storage_kind)
243
345
 
244
346
 
245
347
class KnitContentFactory(ContentFactory):
249
351
    """
250
352
 
251
353
    def __init__(self, key, parents, build_details, sha1, raw_record,
252
 
        annotated, knit=None, network_bytes=None):
 
354
                 annotated, knit=None, network_bytes=None):
253
355
        """Create a KnitContentFactory for key.
254
356
 
255
357
        :param key: The key.
283
385
    def _create_network_bytes(self):
284
386
        """Create a fully serialised network version for transmission."""
285
387
        # storage_kind, key, parents, Noeol, raw_record
286
 
        key_bytes = '\x00'.join(self.key)
 
388
        key_bytes = b'\x00'.join(self.key)
287
389
        if self.parents is None:
288
 
            parent_bytes = 'None:'
 
390
            parent_bytes = b'None:'
289
391
        else:
290
 
            parent_bytes = '\t'.join('\x00'.join(key) for key in self.parents)
 
392
            parent_bytes = b'\t'.join(b'\x00'.join(key)
 
393
                                      for key in self.parents)
291
394
        if self._build_details[1]:
292
 
            noeol = 'N'
 
395
            noeol = b'N'
293
396
        else:
294
 
            noeol = ' '
295
 
        network_bytes = "%s\n%s\n%s\n%s%s" % (self.storage_kind, key_bytes,
 
397
            noeol = b' '
 
398
        network_bytes = b"%s\n%s\n%s\n%s%s" % (
 
399
            self.storage_kind.encode('ascii'), key_bytes,
296
400
            parent_bytes, noeol, self._raw_record)
297
401
        self._network_bytes = network_bytes
298
402
 
301
405
            if self._network_bytes is None:
302
406
                self._create_network_bytes()
303
407
            return self._network_bytes
304
 
        if ('-ft-' in self.storage_kind and
305
 
            storage_kind in ('chunked', 'fulltext')):
306
 
            adapter_key = (self.storage_kind, 'fulltext')
 
408
        if ('-ft-' in self.storage_kind
 
409
                and storage_kind in ('chunked', 'fulltext', 'lines')):
 
410
            adapter_key = (self.storage_kind, storage_kind)
307
411
            adapter_factory = adapter_registry.get(adapter_key)
308
412
            adapter = adapter_factory(None)
309
 
            bytes = adapter.get_bytes(self)
310
 
            if storage_kind == 'chunked':
311
 
                return [bytes]
312
 
            else:
313
 
                return bytes
 
413
            return adapter.get_bytes(self, storage_kind)
314
414
        if self._knit is not None:
315
415
            # Not redundant with direct conversion above - that only handles
316
416
            # fulltext cases.
317
 
            if storage_kind == 'chunked':
 
417
            if storage_kind in ('chunked', 'lines'):
318
418
                return self._knit.get_lines(self.key[0])
319
419
            elif storage_kind == 'fulltext':
320
420
                return self._knit.get_text(self.key[0])
321
421
        raise errors.UnavailableRepresentation(self.key, storage_kind,
322
 
            self.storage_kind)
 
422
                                               self.storage_kind)
 
423
 
 
424
    def iter_bytes_as(self, storage_kind):
 
425
        return iter(self.get_bytes_as(storage_kind))
323
426
 
324
427
 
325
428
class LazyKnitContentFactory(ContentFactory):
342
445
        self.key = key
343
446
        self.parents = parents
344
447
        self.sha1 = None
 
448
        self.size = None
345
449
        self._generator = generator
346
450
        self.storage_kind = "knit-delta-closure"
347
451
        if not first:
355
459
            else:
356
460
                # all the keys etc are contained in the bytes returned in the
357
461
                # first record.
358
 
                return ''
359
 
        if storage_kind in ('chunked', 'fulltext'):
 
462
                return b''
 
463
        if storage_kind in ('chunked', 'fulltext', 'lines'):
360
464
            chunks = self._generator._get_one_work(self.key).text()
361
 
            if storage_kind == 'chunked':
 
465
            if storage_kind in ('chunked', 'lines'):
362
466
                return chunks
363
467
            else:
364
 
                return ''.join(chunks)
365
 
        raise errors.UnavailableRepresentation(self.key, storage_kind,
366
 
            self.storage_kind)
 
468
                return b''.join(chunks)
 
469
        raise errors.UnavailableRepresentation(self.key, storage_kind,
 
470
                                               self.storage_kind)
 
471
 
 
472
    def iter_bytes_as(self, storage_kind):
 
473
        if storage_kind in ('chunked', 'lines'):
 
474
            chunks = self._generator._get_one_work(self.key).text()
 
475
            return iter(chunks)
 
476
        raise errors.UnavailableRepresentation(self.key, storage_kind,
 
477
                                               self.storage_kind)
367
478
 
368
479
 
369
480
def knit_delta_closure_to_records(storage_kind, bytes, line_end):
384
495
    :param bytes: The bytes of the record on the network.
385
496
    """
386
497
    start = line_end
387
 
    line_end = bytes.find('\n', start)
388
 
    key = tuple(bytes[start:line_end].split('\x00'))
 
498
    line_end = bytes.find(b'\n', start)
 
499
    key = tuple(bytes[start:line_end].split(b'\x00'))
389
500
    start = line_end + 1
390
 
    line_end = bytes.find('\n', start)
 
501
    line_end = bytes.find(b'\n', start)
391
502
    parent_line = bytes[start:line_end]
392
 
    if parent_line == 'None:':
 
503
    if parent_line == b'None:':
393
504
        parents = None
394
505
    else:
395
506
        parents = tuple(
396
 
            [tuple(segment.split('\x00')) for segment in parent_line.split('\t')
 
507
            [tuple(segment.split(b'\x00')) for segment in parent_line.split(b'\t')
397
508
             if segment])
398
509
    start = line_end + 1
399
 
    noeol = bytes[start] == 'N'
 
510
    noeol = bytes[start:start + 1] == b'N'
400
511
    if 'ft' in storage_kind:
401
512
        method = 'fulltext'
402
513
    else:
406
517
    raw_record = bytes[start:]
407
518
    annotated = 'annotated' in storage_kind
408
519
    return [KnitContentFactory(key, parents, build_details, None, raw_record,
409
 
        annotated, network_bytes=bytes)]
 
520
                               annotated, network_bytes=bytes)]
410
521
 
411
522
 
412
523
class KnitContent(object):
413
524
    """Content of a knit version to which deltas can be applied.
414
525
 
415
 
    This is always stored in memory as a list of lines with \n at the end,
 
526
    This is always stored in memory as a list of lines with \\n at the end,
416
527
    plus a flag saying if the final ending is really there or not, because that
417
528
    corresponds to the on-disk knit representation.
418
529
    """
450
561
            if n > 0:
451
562
                # knit deltas do not provide reliable info about whether the
452
563
                # last line of a file matches, due to eol handling.
453
 
                if source[s_pos + n -1] != target[t_pos + n -1]:
454
 
                    n-=1
 
564
                if source[s_pos + n - 1] != target[t_pos + n - 1]:
 
565
                    n -= 1
455
566
                if n > 0:
456
567
                    yield s_pos, t_pos, n
457
568
            t_pos += t_len + true_n
458
569
            s_pos = s_end
459
570
        n = target_len - t_pos
460
571
        if n > 0:
461
 
            if source[s_pos + n -1] != target[t_pos + n -1]:
462
 
                n-=1
 
572
            if source[s_pos + n - 1] != target[t_pos + n - 1]:
 
573
                n -= 1
463
574
            if n > 0:
464
575
                yield s_pos, t_pos, n
465
576
        yield s_pos + (target_len - t_pos), target_len, 0
470
581
 
471
582
    def __init__(self, lines):
472
583
        KnitContent.__init__(self)
473
 
        self._lines = lines
 
584
        self._lines = list(lines)
474
585
 
475
586
    def annotate(self):
476
587
        """Return a list of (origin, text) for each content line."""
477
588
        lines = self._lines[:]
478
589
        if self._should_strip_eol:
479
590
            origin, last_line = lines[-1]
480
 
            lines[-1] = (origin, last_line.rstrip('\n'))
 
591
            lines[-1] = (origin, last_line.rstrip(b'\n'))
481
592
        return lines
482
593
 
483
594
    def apply_delta(self, delta, new_version_id):
485
596
        offset = 0
486
597
        lines = self._lines
487
598
        for start, end, count, delta_lines in delta:
488
 
            lines[offset+start:offset+end] = delta_lines
 
599
            lines[offset + start:offset + end] = delta_lines
489
600
            offset = offset + (start - end) + count
490
601
 
491
602
    def text(self):
492
603
        try:
493
604
            lines = [text for origin, text in self._lines]
494
 
        except ValueError, e:
 
605
        except ValueError as e:
495
606
            # most commonly (only?) caused by the internal form of the knit
496
607
            # missing annotation information because of a bug - see thread
497
608
            # around 20071015
498
609
            raise KnitCorrupt(self,
499
 
                "line in annotated knit missing annotation information: %s"
500
 
                % (e,))
 
610
                              "line in annotated knit missing annotation information: %s"
 
611
                              % (e,))
501
612
        if self._should_strip_eol:
502
 
            lines[-1] = lines[-1].rstrip('\n')
 
613
            lines[-1] = lines[-1].rstrip(b'\n')
503
614
        return lines
504
615
 
505
616
    def copy(self):
506
 
        return AnnotatedKnitContent(self._lines[:])
 
617
        return AnnotatedKnitContent(self._lines)
507
618
 
508
619
 
509
620
class PlainKnitContent(KnitContent):
528
639
        offset = 0
529
640
        lines = self._lines
530
641
        for start, end, count, delta_lines in delta:
531
 
            lines[offset+start:offset+end] = delta_lines
 
642
            lines[offset + start:offset + end] = delta_lines
532
643
            offset = offset + (start - end) + count
533
644
        self._version_id = new_version_id
534
645
 
539
650
        lines = self._lines
540
651
        if self._should_strip_eol:
541
652
            lines = lines[:]
542
 
            lines[-1] = lines[-1].rstrip('\n')
 
653
            lines[-1] = lines[-1].rstrip(b'\n')
543
654
        return lines
544
655
 
545
656
 
598
709
        #       but the code itself doesn't really depend on that.
599
710
        #       Figure out a way to not require the overhead of turning the
600
711
        #       list back into tuples.
601
 
        lines = [tuple(line.split(' ', 1)) for line in content]
 
712
        lines = (tuple(line.split(b' ', 1)) for line in content)
602
713
        return AnnotatedKnitContent(lines)
603
714
 
604
 
    def parse_line_delta_iter(self, lines):
605
 
        return iter(self.parse_line_delta(lines))
606
 
 
607
715
    def parse_line_delta(self, lines, version_id, plain=False):
608
716
        """Convert a line based delta into internal representation.
609
717
 
620
728
        """
621
729
        result = []
622
730
        lines = iter(lines)
623
 
        next = lines.next
624
731
 
625
732
        cache = {}
 
733
 
626
734
        def cache_and_return(line):
627
 
            origin, text = line.split(' ', 1)
 
735
            origin, text = line.split(b' ', 1)
628
736
            return cache.setdefault(origin, origin), text
629
737
 
630
738
        # walk through the lines parsing.
632
740
        # loop to minimise any performance impact
633
741
        if plain:
634
742
            for header in lines:
635
 
                start, end, count = [int(n) for n in header.split(',')]
636
 
                contents = [next().split(' ', 1)[1] for i in xrange(count)]
 
743
                start, end, count = [int(n) for n in header.split(b',')]
 
744
                contents = [next(lines).split(b' ', 1)[1]
 
745
                            for _ in range(count)]
637
746
                result.append((start, end, count, contents))
638
747
        else:
639
748
            for header in lines:
640
 
                start, end, count = [int(n) for n in header.split(',')]
641
 
                contents = [tuple(next().split(' ', 1)) for i in xrange(count)]
 
749
                start, end, count = [int(n) for n in header.split(b',')]
 
750
                contents = [tuple(next(lines).split(b' ', 1))
 
751
                            for _ in range(count)]
642
752
                result.append((start, end, count, contents))
643
753
        return result
644
754
 
645
755
    def get_fulltext_content(self, lines):
646
756
        """Extract just the content lines from a fulltext."""
647
 
        return (line.split(' ', 1)[1] for line in lines)
 
757
        return (line.split(b' ', 1)[1] for line in lines)
648
758
 
649
759
    def get_linedelta_content(self, lines):
650
760
        """Extract just the content from a line delta.
653
763
        Only the actual content lines.
654
764
        """
655
765
        lines = iter(lines)
656
 
        next = lines.next
657
766
        for header in lines:
658
 
            header = header.split(',')
 
767
            header = header.split(b',')
659
768
            count = int(header[2])
660
 
            for i in xrange(count):
661
 
                origin, text = next().split(' ', 1)
 
769
            for _ in range(count):
 
770
                origin, text = next(lines).split(b' ', 1)
662
771
                yield text
663
772
 
664
773
    def lower_fulltext(self, content):
666
775
 
667
776
        see parse_fulltext which this inverts.
668
777
        """
669
 
        return ['%s %s' % (o, t) for o, t in content._lines]
 
778
        return [b'%s %s' % (o, t) for o, t in content._lines]
670
779
 
671
780
    def lower_line_delta(self, delta):
672
781
        """convert a delta into a serializable form.
677
786
        #       the origin is a valid utf-8 line, eventually we could remove it
678
787
        out = []
679
788
        for start, end, c, lines in delta:
680
 
            out.append('%d,%d,%d\n' % (start, end, c))
681
 
            out.extend(origin + ' ' + text
 
789
            out.append(b'%d,%d,%d\n' % (start, end, c))
 
790
            out.extend(origin + b' ' + text
682
791
                       for origin, text in lines)
683
792
        return out
684
793
 
686
795
        content = knit._get_content(key)
687
796
        # adjust for the fact that serialised annotations are only key suffixes
688
797
        # for this factory.
689
 
        if type(key) is tuple:
 
798
        if isinstance(key, tuple):
690
799
            prefix = key[:-1]
691
800
            origins = content.annotate()
692
801
            result = []
721
830
        while cur < num_lines:
722
831
            header = lines[cur]
723
832
            cur += 1
724
 
            start, end, c = [int(n) for n in header.split(',')]
725
 
            yield start, end, c, lines[cur:cur+c]
 
833
            start, end, c = [int(n) for n in header.split(b',')]
 
834
            yield start, end, c, lines[cur:cur + c]
726
835
            cur += c
727
836
 
728
837
    def parse_line_delta(self, lines, version_id):
739
848
        Only the actual content lines.
740
849
        """
741
850
        lines = iter(lines)
742
 
        next = lines.next
743
851
        for header in lines:
744
 
            header = header.split(',')
 
852
            header = header.split(b',')
745
853
            count = int(header[2])
746
 
            for i in xrange(count):
747
 
                yield next()
 
854
            for _ in range(count):
 
855
                yield next(lines)
748
856
 
749
857
    def lower_fulltext(self, content):
750
858
        return content.text()
752
860
    def lower_line_delta(self, delta):
753
861
        out = []
754
862
        for start, end, c, lines in delta:
755
 
            out.append('%d,%d,%d\n' % (start, end, c))
 
863
            out.append(b'%d,%d,%d\n' % (start, end, c))
756
864
            out.extend(lines)
757
865
        return out
758
866
 
761
869
        return annotator.annotate_flat(key)
762
870
 
763
871
 
764
 
 
765
872
def make_file_factory(annotated, mapper):
766
873
    """Create a factory for creating a file based KnitVersionedFiles.
767
874
 
772
879
    :param mapper: The mapper from keys to paths.
773
880
    """
774
881
    def factory(transport):
775
 
        index = _KndxIndex(transport, mapper, lambda:None, lambda:True, lambda:True)
 
882
        index = _KndxIndex(transport, mapper, lambda: None,
 
883
                           lambda: True, lambda: True)
776
884
        access = _KnitKeyAccess(transport, mapper)
777
885
        return KnitVersionedFiles(index, access, annotated=annotated)
778
886
    return factory
799
907
        else:
800
908
            max_delta_chain = 0
801
909
        graph_index = _mod_index.InMemoryGraphIndex(reference_lists=ref_length,
802
 
            key_elements=keylength)
 
910
                                                    key_elements=keylength)
803
911
        stream = transport.open_write_stream('newpack')
804
912
        writer = pack.ContainerWriter(stream.write)
805
913
        writer.begin()
806
 
        index = _KnitGraphIndex(graph_index, lambda:True, parents=parents,
807
 
            deltas=delta, add_callback=graph_index.add_nodes)
808
 
        access = _DirectPackAccess({})
 
914
        index = _KnitGraphIndex(graph_index, lambda: True, parents=parents,
 
915
                                deltas=delta, add_callback=graph_index.add_nodes)
 
916
        access = pack_repo._DirectPackAccess({})
809
917
        access.set_writer(writer, graph_index, (transport, 'newpack'))
810
918
        result = KnitVersionedFiles(index, access,
811
 
            max_delta_chain=max_delta_chain)
 
919
                                    max_delta_chain=max_delta_chain)
812
920
        result.stream = stream
813
921
        result.writer = writer
814
922
        return result
845
953
            if compression_parent not in all_build_index_memos:
846
954
                next_keys.add(compression_parent)
847
955
        build_keys = next_keys
848
 
    return sum([index_memo[2] for index_memo
849
 
                in all_build_index_memos.itervalues()])
850
 
 
851
 
 
852
 
class KnitVersionedFiles(VersionedFiles):
 
956
    return sum(index_memo[2]
 
957
               for index_memo in viewvalues(all_build_index_memos))
 
958
 
 
959
 
 
960
class KnitVersionedFiles(VersionedFilesWithFallbacks):
853
961
    """Storage for many versioned files using knit compression.
854
962
 
855
963
    Backend storage is managed by indices and data objects.
873
981
            stored during insertion.
874
982
        :param reload_func: An function that can be called if we think we need
875
983
            to reload the pack listing and try again. See
876
 
            'bzrlib.repofmt.pack_repo.AggregateIndex' for the signature.
 
984
            'breezy.bzr.pack_repo.AggregateIndex' for the signature.
877
985
        """
878
986
        self._index = index
879
987
        self._access = data_access
882
990
            self._factory = KnitAnnotateFactory()
883
991
        else:
884
992
            self._factory = KnitPlainFactory()
885
 
        self._fallback_vfs = []
 
993
        self._immediate_fallback_vfs = []
886
994
        self._reload_func = reload_func
887
995
 
888
996
    def __repr__(self):
891
999
            self._index,
892
1000
            self._access)
893
1001
 
 
1002
    def without_fallbacks(self):
 
1003
        """Return a clone of this object without any fallbacks configured."""
 
1004
        return KnitVersionedFiles(self._index, self._access,
 
1005
                                  self._max_delta_chain, self._factory.annotated,
 
1006
                                  self._reload_func)
 
1007
 
894
1008
    def add_fallback_versioned_files(self, a_versioned_files):
895
1009
        """Add a source of texts for texts not present in this knit.
896
1010
 
897
1011
        :param a_versioned_files: A VersionedFiles object.
898
1012
        """
899
 
        self._fallback_vfs.append(a_versioned_files)
 
1013
        self._immediate_fallback_vfs.append(a_versioned_files)
900
1014
 
901
1015
    def add_lines(self, key, parents, lines, parent_texts=None,
902
 
        left_matching_blocks=None, nostore_sha=None, random_id=False,
903
 
        check_content=True):
 
1016
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
 
1017
                  check_content=True):
904
1018
        """See VersionedFiles.add_lines()."""
905
1019
        self._index._check_write_ok()
906
1020
        self._check_add(key, lines, random_id, check_content)
909
1023
            # indexes can't directly store that, so we give them
910
1024
            # an empty tuple instead.
911
1025
            parents = ()
912
 
        line_bytes = ''.join(lines)
 
1026
        line_bytes = b''.join(lines)
913
1027
        return self._add(key, lines, parents,
914
 
            parent_texts, left_matching_blocks, nostore_sha, random_id,
915
 
            line_bytes=line_bytes)
 
1028
                         parent_texts, left_matching_blocks, nostore_sha, random_id,
 
1029
                         line_bytes=line_bytes)
916
1030
 
917
 
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
918
 
        """See VersionedFiles._add_text()."""
 
1031
    def add_content(self, content_factory, parent_texts=None,
 
1032
                    left_matching_blocks=None, nostore_sha=None,
 
1033
                    random_id=False):
 
1034
        """See VersionedFiles.add_content()."""
919
1035
        self._index._check_write_ok()
 
1036
        key = content_factory.key
 
1037
        parents = content_factory.parents
920
1038
        self._check_add(key, None, random_id, check_content=False)
921
 
        if text.__class__ is not str:
922
 
            raise errors.BzrBadParameterUnicode("text")
923
1039
        if parents is None:
924
1040
            # The caller might pass None if there is no graph data, but kndx
925
1041
            # indexes can't directly store that, so we give them
926
1042
            # an empty tuple instead.
927
1043
            parents = ()
928
 
        return self._add(key, None, parents,
929
 
            None, None, nostore_sha, random_id,
930
 
            line_bytes=text)
 
1044
        lines = content_factory.get_bytes_as('lines')
 
1045
        line_bytes = content_factory.get_bytes_as('fulltext')
 
1046
        return self._add(key, lines, parents,
 
1047
                         parent_texts, left_matching_blocks, nostore_sha, random_id,
 
1048
                         line_bytes=line_bytes)
931
1049
 
932
1050
    def _add(self, key, lines, parents, parent_texts,
933
 
        left_matching_blocks, nostore_sha, random_id,
934
 
        line_bytes):
 
1051
             left_matching_blocks, nostore_sha, random_id,
 
1052
             line_bytes):
935
1053
        """Add a set of lines on top of version specified by parents.
936
1054
 
937
1055
        Any versions not present will be converted into ghosts.
963
1081
                present_parents.append(parent)
964
1082
 
965
1083
        # Currently we can only compress against the left most present parent.
966
 
        if (len(present_parents) == 0 or
967
 
            present_parents[0] != parents[0]):
 
1084
        if (len(present_parents) == 0
 
1085
                or present_parents[0] != parents[0]):
968
1086
            delta = False
969
1087
        else:
970
1088
            # To speed the extract of texts the delta chain is limited
978
1096
        # Note: line_bytes is not modified to add a newline, that is tracked
979
1097
        #       via the no_eol flag. 'lines' *is* modified, because that is the
980
1098
        #       general values needed by the Content code.
981
 
        if line_bytes and line_bytes[-1] != '\n':
982
 
            options.append('no-eol')
 
1099
        if line_bytes and not line_bytes.endswith(b'\n'):
 
1100
            options.append(b'no-eol')
983
1101
            no_eol = True
984
1102
            # Copy the existing list, or create a new one
985
1103
            if lines is None:
987
1105
            else:
988
1106
                lines = lines[:]
989
1107
            # Replace the last line with one that ends in a final newline
990
 
            lines[-1] = lines[-1] + '\n'
 
1108
            lines[-1] = lines[-1] + b'\n'
991
1109
        if lines is None:
992
1110
            lines = osutils.split_lines(line_bytes)
993
1111
 
994
1112
        for element in key[:-1]:
995
 
            if type(element) is not str:
996
 
                raise TypeError("key contains non-strings: %r" % (key,))
 
1113
            if not isinstance(element, bytes):
 
1114
                raise TypeError("key contains non-bytestrings: %r" % (key,))
997
1115
        if key[-1] is None:
998
 
            key = key[:-1] + ('sha1:' + digest,)
999
 
        elif type(key[-1]) is not str:
1000
 
                raise TypeError("key contains non-strings: %r" % (key,))
 
1116
            key = key[:-1] + (b'sha1:' + digest,)
 
1117
        elif not isinstance(key[-1], bytes):
 
1118
            raise TypeError("key contains non-bytestrings: %r" % (key,))
1001
1119
        # Knit hunks are still last-element only
1002
1120
        version_id = key[-1]
1003
1121
        content = self._factory.make(lines, version_id)
1008
1126
        if delta or (self._factory.annotated and len(present_parents) > 0):
1009
1127
            # Merge annotations from parent texts if needed.
1010
1128
            delta_hunks = self._merge_annotations(content, present_parents,
1011
 
                parent_texts, delta, self._factory.annotated,
1012
 
                left_matching_blocks)
 
1129
                                                  parent_texts, delta, self._factory.annotated,
 
1130
                                                  left_matching_blocks)
1013
1131
 
1014
1132
        if delta:
1015
 
            options.append('line-delta')
 
1133
            options.append(b'line-delta')
1016
1134
            store_lines = self._factory.lower_line_delta(delta_hunks)
1017
 
            size, bytes = self._record_to_data(key, digest,
1018
 
                store_lines)
 
1135
            size, data = self._record_to_data(key, digest, store_lines)
1019
1136
        else:
1020
 
            options.append('fulltext')
 
1137
            options.append(b'fulltext')
1021
1138
            # isinstance is slower and we have no hierarchy.
1022
1139
            if self._factory.__class__ is KnitPlainFactory:
1023
1140
                # Use the already joined bytes saving iteration time in
1024
1141
                # _record_to_data.
1025
1142
                dense_lines = [line_bytes]
1026
1143
                if no_eol:
1027
 
                    dense_lines.append('\n')
1028
 
                size, bytes = self._record_to_data(key, digest,
1029
 
                    lines, dense_lines)
 
1144
                    dense_lines.append(b'\n')
 
1145
                size, data = self._record_to_data(key, digest,
 
1146
                                                  lines, dense_lines)
1030
1147
            else:
1031
1148
                # get mixed annotation + content and feed it into the
1032
1149
                # serialiser.
1033
1150
                store_lines = self._factory.lower_fulltext(content)
1034
 
                size, bytes = self._record_to_data(key, digest,
1035
 
                    store_lines)
 
1151
                size, data = self._record_to_data(key, digest, store_lines)
1036
1152
 
1037
 
        access_memo = self._access.add_raw_records([(key, size)], bytes)[0]
 
1153
        access_memo = self._access.add_raw_record(key, size, data)
1038
1154
        self._index.add_records(
1039
1155
            ((key, options, access_memo, parents),),
1040
1156
            random_id=random_id)
1066
1182
            if self._index.get_method(key) != 'fulltext':
1067
1183
                compression_parent = parent_map[key][0]
1068
1184
                if compression_parent not in parent_map:
1069
 
                    raise errors.KnitCorrupt(self,
1070
 
                        "Missing basis parent %s for %s" % (
1071
 
                        compression_parent, key))
1072
 
        for fallback_vfs in self._fallback_vfs:
 
1185
                    raise KnitCorrupt(self,
 
1186
                                      "Missing basis parent %s for %s" % (
 
1187
                                          compression_parent, key))
 
1188
        for fallback_vfs in self._immediate_fallback_vfs:
1073
1189
            fallback_vfs.check()
1074
1190
 
1075
1191
    def _check_add(self, key, lines, random_id, check_content):
1076
1192
        """check that version_id and lines are safe to add."""
 
1193
        if not all(isinstance(x, bytes) or x is None for x in key):
 
1194
            raise TypeError(key)
1077
1195
        version_id = key[-1]
1078
1196
        if version_id is not None:
1079
1197
            if contains_whitespace(version_id):
1099
1217
        """
1100
1218
        if rec[1] != version_id:
1101
1219
            raise KnitCorrupt(self,
1102
 
                'unexpected version, wanted %r, got %r' % (version_id, rec[1]))
 
1220
                              'unexpected version, wanted %r, got %r' % (version_id, rec[1]))
1103
1221
 
1104
1222
    def _check_should_delta(self, parent):
1105
1223
        """Iterate back through the parent listing, looking for a fulltext.
1114
1232
        """
1115
1233
        delta_size = 0
1116
1234
        fulltext_size = None
1117
 
        for count in xrange(self._max_delta_chain):
 
1235
        for count in range(self._max_delta_chain):
1118
1236
            try:
1119
1237
                # Note that this only looks in the index of this particular
1120
1238
                # KnitVersionedFiles, not in the fallbacks.  This ensures that
1122
1240
                # boundaries.
1123
1241
                build_details = self._index.get_build_details([parent])
1124
1242
                parent_details = build_details[parent]
1125
 
            except (RevisionNotPresent, KeyError), e:
 
1243
            except (RevisionNotPresent, KeyError) as e:
1126
1244
                # Some basis is not locally present: always fulltext
1127
1245
                return False
1128
1246
            index_memo, compression_parent, _, _ = parent_details
1153
1271
 
1154
1272
        A dict of key to (record_details, index_memo, next, parents) is
1155
1273
        returned.
1156
 
        method is the way referenced data should be applied.
1157
 
        index_memo is the handle to pass to the data access to actually get the
1158
 
            data
1159
 
        next is the build-parent of the version, or None for fulltexts.
1160
 
        parents is the version_ids of the parents of this version
1161
 
 
1162
 
        :param allow_missing: If True do not raise an error on a missing component,
1163
 
            just ignore it.
 
1274
 
 
1275
        * method is the way referenced data should be applied.
 
1276
        * index_memo is the handle to pass to the data access to actually get
 
1277
          the data
 
1278
        * next is the build-parent of the version, or None for fulltexts.
 
1279
        * parents is the version_ids of the parents of this version
 
1280
 
 
1281
        :param allow_missing: If True do not raise an error on a missing
 
1282
            component, just ignore it.
1164
1283
        """
1165
1284
        component_data = {}
1166
1285
        pending_components = keys
1168
1287
            build_details = self._index.get_build_details(pending_components)
1169
1288
            current_components = set(pending_components)
1170
1289
            pending_components = set()
1171
 
            for key, details in build_details.iteritems():
 
1290
            for key, details in viewitems(build_details):
1172
1291
                (index_memo, compression_parent, parents,
1173
1292
                 record_details) = details
1174
 
                method = record_details[0]
1175
1293
                if compression_parent is not None:
1176
1294
                    pending_components.add(compression_parent)
1177
 
                component_data[key] = self._build_details_to_components(details)
 
1295
                component_data[key] = self._build_details_to_components(
 
1296
                    details)
1178
1297
            missing = current_components.difference(build_details)
1179
1298
            if missing and not allow_missing:
1180
1299
                raise errors.RevisionNotPresent(missing.pop(), self)
1192
1311
        generator = _VFContentMapGenerator(self, [key])
1193
1312
        return generator._get_content(key)
1194
1313
 
1195
 
    def get_known_graph_ancestry(self, keys):
1196
 
        """Get a KnownGraph instance with the ancestry of keys."""
1197
 
        parent_map, missing_keys = self._index.find_ancestry(keys)
1198
 
        for fallback in self._fallback_vfs:
1199
 
            if not missing_keys:
1200
 
                break
1201
 
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
1202
 
                                                missing_keys)
1203
 
            parent_map.update(f_parent_map)
1204
 
            missing_keys = f_missing_keys
1205
 
        kg = _mod_graph.KnownGraph(parent_map)
1206
 
        return kg
1207
 
 
1208
1314
    def get_parent_map(self, keys):
1209
1315
        """Get a map of the graph parents of keys.
1210
1316
 
1225
1331
            and so on.
1226
1332
        """
1227
1333
        result = {}
1228
 
        sources = [self._index] + self._fallback_vfs
 
1334
        sources = [self._index] + self._immediate_fallback_vfs
1229
1335
        source_results = []
1230
1336
        missing = set(keys)
1231
1337
        for source in sources:
1241
1347
        """Produce a dictionary of knit records.
1242
1348
 
1243
1349
        :return: {key:(record, record_details, digest, next)}
1244
 
            record
1245
 
                data returned from read_records (a KnitContentobject)
1246
 
            record_details
1247
 
                opaque information to pass to parse_record
1248
 
            digest
1249
 
                SHA1 digest of the full text after all steps are done
1250
 
            next
1251
 
                build-parent of the version, i.e. the leftmost ancestor.
 
1350
 
 
1351
            * record: data returned from read_records (a KnitContentobject)
 
1352
            * record_details: opaque information to pass to parse_record
 
1353
            * digest: SHA1 digest of the full text after all steps are done
 
1354
            * next: build-parent of the version, i.e. the leftmost ancestor.
1252
1355
                Will be None if the record is not a delta.
 
1356
 
1253
1357
        :param keys: The keys to build a map for
1254
1358
        :param allow_missing: If some records are missing, rather than
1255
1359
            error, just return the data that could be generated.
1256
1360
        """
1257
1361
        raw_map = self._get_record_map_unparsed(keys,
1258
 
            allow_missing=allow_missing)
 
1362
                                                allow_missing=allow_missing)
1259
1363
        return self._raw_map_to_record_map(raw_map)
1260
1364
 
1261
1365
    def _raw_map_to_record_map(self, raw_map):
1286
1390
        while True:
1287
1391
            try:
1288
1392
                position_map = self._get_components_positions(keys,
1289
 
                    allow_missing=allow_missing)
 
1393
                                                              allow_missing=allow_missing)
1290
1394
                # key = component_id, r = record_details, i_m = index_memo,
1291
1395
                # n = next
1292
1396
                records = [(key, i_m) for key, (r, i_m, n)
1293
 
                                       in position_map.iteritems()]
 
1397
                           in viewitems(position_map)]
1294
1398
                # Sort by the index memo, so that we request records from the
1295
1399
                # same pack file together, and in forward-sorted order
1296
1400
                records.sort(key=operator.itemgetter(1))
1299
1403
                    (record_details, index_memo, next) = position_map[key]
1300
1404
                    raw_record_map[key] = data, record_details, next
1301
1405
                return raw_record_map
1302
 
            except errors.RetryWithNewPacks, e:
 
1406
            except errors.RetryWithNewPacks as e:
1303
1407
                self._access.reload_or_raise(e)
1304
1408
 
1305
1409
    @classmethod
1325
1429
        prefix_order = []
1326
1430
        for key in keys:
1327
1431
            if len(key) == 1:
1328
 
                prefix = ''
 
1432
                prefix = b''
1329
1433
            else:
1330
1434
                prefix = key[0]
1331
1435
 
1404
1508
            try:
1405
1509
                keys = set(remaining_keys)
1406
1510
                for content_factory in self._get_remaining_record_stream(keys,
1407
 
                                            ordering, include_delta_closure):
 
1511
                                                                         ordering, include_delta_closure):
1408
1512
                    remaining_keys.discard(content_factory.key)
1409
1513
                    yield content_factory
1410
1514
                return
1411
 
            except errors.RetryWithNewPacks, e:
 
1515
            except errors.RetryWithNewPacks as e:
1412
1516
                self._access.reload_or_raise(e)
1413
1517
 
1414
1518
    def _get_remaining_record_stream(self, keys, ordering,
1415
1519
                                     include_delta_closure):
1416
1520
        """This function is the 'retry' portion for get_record_stream."""
1417
1521
        if include_delta_closure:
1418
 
            positions = self._get_components_positions(keys, allow_missing=True)
 
1522
            positions = self._get_components_positions(
 
1523
                keys, allow_missing=True)
1419
1524
        else:
1420
1525
            build_details = self._index.get_build_details(keys)
1421
1526
            # map from key to
1422
1527
            # (record_details, access_memo, compression_parent_key)
1423
1528
            positions = dict((key, self._build_details_to_components(details))
1424
 
                for key, details in build_details.iteritems())
 
1529
                             for key, details in viewitems(build_details))
1425
1530
        absent_keys = keys.difference(set(positions))
1426
1531
        # There may be more absent keys : if we're missing the basis component
1427
1532
        # and are trying to include the delta closure.
1479
1584
        else:
1480
1585
            if ordering != 'unordered':
1481
1586
                raise AssertionError('valid values for ordering are:'
1482
 
                    ' "unordered", "groupcompress" or "topological" not: %r'
1483
 
                    % (ordering,))
 
1587
                                     ' "unordered", "groupcompress" or "topological" not: %r'
 
1588
                                     % (ordering,))
1484
1589
            # Just group by source; remote sources first.
1485
1590
            present_keys = []
1486
1591
            source_keys = []
1523
1628
                    for key, raw_data in self._read_records_iter_unchecked(records):
1524
1629
                        (record_details, index_memo, _) = positions[key]
1525
1630
                        yield KnitContentFactory(key, global_map[key],
1526
 
                            record_details, None, raw_data, self._factory.annotated, None)
 
1631
                                                 record_details, None, raw_data, self._factory.annotated, None)
1527
1632
                else:
1528
 
                    vf = self._fallback_vfs[parent_maps.index(source) - 1]
 
1633
                    vf = self._immediate_fallback_vfs[parent_maps.index(
 
1634
                        source) - 1]
1529
1635
                    for record in vf.get_record_stream(keys, ordering,
1530
 
                        include_delta_closure):
 
1636
                                                       include_delta_closure):
1531
1637
                        yield record
1532
1638
 
1533
1639
    def get_sha1s(self, keys):
1535
1641
        missing = set(keys)
1536
1642
        record_map = self._get_record_map(missing, allow_missing=True)
1537
1643
        result = {}
1538
 
        for key, details in record_map.iteritems():
 
1644
        for key, details in viewitems(record_map):
1539
1645
            if key not in missing:
1540
1646
                continue
1541
1647
            # record entry 2 is the 'digest'.
1542
1648
            result[key] = details[2]
1543
1649
        missing.difference_update(set(result))
1544
 
        for source in self._fallback_vfs:
 
1650
        for source in self._immediate_fallback_vfs:
1545
1651
            if not missing:
1546
1652
                break
1547
1653
            new_result = source.get_sha1s(missing)
1572
1678
        else:
1573
1679
            # self is not annotated, but we can strip annotations cheaply.
1574
1680
            annotated = ""
1575
 
            convertibles = set(["knit-annotated-ft-gz"])
 
1681
            convertibles = {"knit-annotated-ft-gz"}
1576
1682
            if self._max_delta_chain:
1577
1683
                delta_types.add("knit-annotated-delta-gz")
1578
1684
                convertibles.add("knit-annotated-delta-gz")
1616
1722
            # Raise an error when a record is missing.
1617
1723
            if record.storage_kind == 'absent':
1618
1724
                raise RevisionNotPresent([record.key], self)
1619
 
            elif ((record.storage_kind in knit_types)
1620
 
                  and (compression_parent is None
1621
 
                       or not self._fallback_vfs
1622
 
                       or self._index.has_key(compression_parent)
1623
 
                       or not self.has_key(compression_parent))):
 
1725
            elif ((record.storage_kind in knit_types) and
 
1726
                  (compression_parent is None or
 
1727
                   not self._immediate_fallback_vfs or
 
1728
                   compression_parent in self._index or
 
1729
                   compression_parent not in self)):
1624
1730
                # we can insert the knit record literally if either it has no
1625
1731
                # compression parent OR we already have its basis in this kvf
1626
1732
                # OR the basis is not present even in the fallbacks.  In the
1628
1734
                # will be well, or it won't turn up at all and we'll raise an
1629
1735
                # error at the end.
1630
1736
                #
1631
 
                # TODO: self.has_key is somewhat redundant with
1632
 
                # self._index.has_key; we really want something that directly
 
1737
                # TODO: self.__contains__ is somewhat redundant with
 
1738
                # self._index.__contains__; we really want something that directly
1633
1739
                # asks if it's only present in the fallbacks. -- mbp 20081119
1634
1740
                if record.storage_kind not in native_types:
1635
1741
                    try:
1638
1744
                    except KeyError:
1639
1745
                        adapter_key = (record.storage_kind, "knit-ft-gz")
1640
1746
                        adapter = get_adapter(adapter_key)
1641
 
                    bytes = adapter.get_bytes(record)
 
1747
                    bytes = adapter.get_bytes(record, adapter_key[1])
1642
1748
                else:
1643
1749
                    # It's a knit record, it has a _raw_record field (even if
1644
1750
                    # it was reconstituted from a network stream).
1645
1751
                    bytes = record._raw_record
1646
 
                options = [record._build_details[0]]
 
1752
                options = [record._build_details[0].encode('ascii')]
1647
1753
                if record._build_details[1]:
1648
 
                    options.append('no-eol')
 
1754
                    options.append(b'no-eol')
1649
1755
                # Just blat it across.
1650
1756
                # Note: This does end up adding data on duplicate keys. As
1651
1757
                # modern repositories use atomic insertions this should not
1654
1760
                # deprecated format this is tolerable. It can be fixed if
1655
1761
                # needed by in the kndx index support raising on a duplicate
1656
1762
                # add with identical parents and options.
1657
 
                access_memo = self._access.add_raw_records(
1658
 
                    [(record.key, len(bytes))], bytes)[0]
 
1763
                access_memo = self._access.add_raw_record(
 
1764
                    record.key, len(bytes), [bytes])
1659
1765
                index_entry = (record.key, options, access_memo, parents)
1660
 
                if 'fulltext' not in options:
 
1766
                if b'fulltext' not in options:
1661
1767
                    # Not a fulltext, so we need to make sure the compression
1662
1768
                    # parent will also be present.
1663
1769
                    # Note that pack backed knits don't need to buffer here
1668
1774
                    #
1669
1775
                    # They're required to be physically in this
1670
1776
                    # KnitVersionedFiles, not in a fallback.
1671
 
                    if not self._index.has_key(compression_parent):
 
1777
                    if compression_parent not in self._index:
1672
1778
                        pending = buffered_index_entries.setdefault(
1673
1779
                            compression_parent, [])
1674
1780
                        pending.append(index_entry)
1675
1781
                        buffered = True
1676
1782
                if not buffered:
1677
1783
                    self._index.add_records([index_entry])
1678
 
            elif record.storage_kind == 'chunked':
1679
 
                self.add_lines(record.key, parents,
1680
 
                    osutils.chunks_to_lines(record.get_bytes_as('chunked')))
 
1784
            elif record.storage_kind in ('chunked', 'file'):
 
1785
                self.add_lines(record.key, parents, record.get_bytes_as('lines'))
1681
1786
            else:
1682
1787
                # Not suitable for direct insertion as a
1683
1788
                # delta, either because it's not the right format, or this
1687
1792
                self._access.flush()
1688
1793
                try:
1689
1794
                    # Try getting a fulltext directly from the record.
1690
 
                    bytes = record.get_bytes_as('fulltext')
 
1795
                    lines = record.get_bytes_as('lines')
1691
1796
                except errors.UnavailableRepresentation:
1692
 
                    adapter_key = record.storage_kind, 'fulltext'
 
1797
                    adapter_key = record.storage_kind, 'lines'
1693
1798
                    adapter = get_adapter(adapter_key)
1694
 
                    bytes = adapter.get_bytes(record)
1695
 
                lines = split_lines(bytes)
 
1799
                    lines = adapter.get_bytes(record, 'lines')
1696
1800
                try:
1697
1801
                    self.add_lines(record.key, parents, lines)
1698
1802
                except errors.RevisionAlreadyPresent:
1767
1871
                # we need key, position, length
1768
1872
                key_records = []
1769
1873
                build_details = self._index.get_build_details(keys)
1770
 
                for key, details in build_details.iteritems():
 
1874
                for key, details in viewitems(build_details):
1771
1875
                    if key in keys:
1772
1876
                        key_records.append((key, details[0]))
1773
1877
                records_iter = enumerate(self._read_records_iter(key_records))
1774
1878
                for (key_idx, (key, data, sha_value)) in records_iter:
1775
 
                    pb.update('Walking content', key_idx, total)
 
1879
                    pb.update(gettext('Walking content'), key_idx, total)
1776
1880
                    compression_parent = build_details[key][1]
1777
1881
                    if compression_parent is None:
1778
1882
                        # fulltext
1779
 
                        line_iterator = self._factory.get_fulltext_content(data)
 
1883
                        line_iterator = self._factory.get_fulltext_content(
 
1884
                            data)
1780
1885
                    else:
1781
1886
                        # Delta
1782
 
                        line_iterator = self._factory.get_linedelta_content(data)
 
1887
                        line_iterator = self._factory.get_linedelta_content(
 
1888
                            data)
1783
1889
                    # Now that we are yielding the data for this key, remove it
1784
1890
                    # from the list
1785
1891
                    keys.remove(key)
1790
1896
                    for line in line_iterator:
1791
1897
                        yield line, key
1792
1898
                done = True
1793
 
            except errors.RetryWithNewPacks, e:
 
1899
            except errors.RetryWithNewPacks as e:
1794
1900
                self._access.reload_or_raise(e)
1795
1901
        # If there are still keys we've not yet found, we look in the fallback
1796
1902
        # vfs, and hope to find them there.  Note that if the keys are found
1797
1903
        # but had no changes or no content, the fallback may not return
1798
1904
        # anything.
1799
 
        if keys and not self._fallback_vfs:
 
1905
        if keys and not self._immediate_fallback_vfs:
1800
1906
            # XXX: strictly the second parameter is meant to be the file id
1801
1907
            # but it's not easily accessible here.
1802
1908
            raise RevisionNotPresent(keys, repr(self))
1803
 
        for source in self._fallback_vfs:
 
1909
        for source in self._immediate_fallback_vfs:
1804
1910
            if not keys:
1805
1911
                break
1806
1912
            source_keys = set()
1808
1914
                source_keys.add(key)
1809
1915
                yield line, key
1810
1916
            keys.difference_update(source_keys)
1811
 
        pb.update('Walking content', total, total)
 
1917
        pb.update(gettext('Walking content'), total, total)
1812
1918
 
1813
1919
    def _make_line_delta(self, delta_seq, new_content):
1814
1920
        """Generate a line delta from delta_seq and new_content."""
1816
1922
        for op in delta_seq.get_opcodes():
1817
1923
            if op[0] == 'equal':
1818
1924
                continue
1819
 
            diff_hunks.append((op[1], op[2], op[4]-op[3], new_content._lines[op[3]:op[4]]))
 
1925
            diff_hunks.append(
 
1926
                (op[1], op[2], op[4] - op[3], new_content._lines[op[3]:op[4]]))
1820
1927
        return diff_hunks
1821
1928
 
1822
1929
    def _merge_annotations(self, content, parents, parent_texts={},
1846
1953
                    # this copies (origin, text) pairs across to the new
1847
1954
                    # content for any line that matches the last-checked
1848
1955
                    # parent.
1849
 
                    content._lines[j:j+n] = merge_content._lines[i:i+n]
 
1956
                    content._lines[j:j + n] = merge_content._lines[i:i + n]
1850
1957
            # XXX: Robert says the following block is a workaround for a
1851
1958
            # now-fixed bug and it can probably be deleted. -- mbp 20080618
1852
 
            if content._lines and content._lines[-1][1][-1] != '\n':
 
1959
            if content._lines and not content._lines[-1][1].endswith(b'\n'):
1853
1960
                # The copied annotation was from a line without a trailing EOL,
1854
1961
                # reinstate one for the content object, to ensure correct
1855
1962
                # serialization.
1856
 
                line = content._lines[-1][1] + '\n'
 
1963
                line = content._lines[-1][1] + b'\n'
1857
1964
                content._lines[-1] = (content._lines[-1][0], line)
1858
1965
        if delta:
1859
1966
            if delta_seq is None:
1861
1968
                new_texts = content.text()
1862
1969
                old_texts = reference_content.text()
1863
1970
                delta_seq = patiencediff.PatienceSequenceMatcher(
1864
 
                                                 None, old_texts, new_texts)
 
1971
                    None, old_texts, new_texts)
1865
1972
            return self._make_line_delta(delta_seq, content)
1866
1973
 
1867
1974
    def _parse_record(self, version_id, data):
1879
1986
        :return: the header and the decompressor stream.
1880
1987
                 as (stream, header_record)
1881
1988
        """
1882
 
        df = tuned_gzip.GzipFile(mode='rb', fileobj=StringIO(raw_data))
 
1989
        df = gzip.GzipFile(mode='rb', fileobj=BytesIO(raw_data))
1883
1990
        try:
1884
1991
            # Current serialise
1885
1992
            rec = self._check_header(key, df.readline())
1886
 
        except Exception, e:
 
1993
        except Exception as e:
1887
1994
            raise KnitCorrupt(self,
1888
1995
                              "While reading {%s} got %s(%s)"
1889
1996
                              % (key, e.__class__.__name__, str(e)))
1894
2001
        # 4168 calls in 2880 217 internal
1895
2002
        # 4168 calls to _parse_record_header in 2121
1896
2003
        # 4168 calls to readlines in 330
1897
 
        df = tuned_gzip.GzipFile(mode='rb', fileobj=StringIO(data))
1898
 
        try:
1899
 
            record_contents = df.readlines()
1900
 
        except Exception, e:
1901
 
            raise KnitCorrupt(self, "Corrupt compressed record %r, got %s(%s)" %
1902
 
                (data, e.__class__.__name__, str(e)))
1903
 
        header = record_contents.pop(0)
1904
 
        rec = self._split_header(header)
1905
 
        last_line = record_contents.pop()
1906
 
        if len(record_contents) != int(rec[2]):
1907
 
            raise KnitCorrupt(self,
1908
 
                              'incorrect number of lines %s != %s'
1909
 
                              ' for version {%s} %s'
1910
 
                              % (len(record_contents), int(rec[2]),
1911
 
                                 rec[1], record_contents))
1912
 
        if last_line != 'end %s\n' % rec[1]:
1913
 
            raise KnitCorrupt(self,
1914
 
                              'unexpected version end line %r, wanted %r'
1915
 
                              % (last_line, rec[1]))
1916
 
        df.close()
 
2004
        with gzip.GzipFile(mode='rb', fileobj=BytesIO(data)) as df:
 
2005
            try:
 
2006
                record_contents = df.readlines()
 
2007
            except Exception as e:
 
2008
                raise KnitCorrupt(self, "Corrupt compressed record %r, got %s(%s)" %
 
2009
                                  (data, e.__class__.__name__, str(e)))
 
2010
            header = record_contents.pop(0)
 
2011
            rec = self._split_header(header)
 
2012
            last_line = record_contents.pop()
 
2013
            if len(record_contents) != int(rec[2]):
 
2014
                raise KnitCorrupt(self,
 
2015
                                  'incorrect number of lines %s != %s'
 
2016
                                  ' for version {%s} %s'
 
2017
                                  % (len(record_contents), int(rec[2]),
 
2018
                                     rec[1], record_contents))
 
2019
            if last_line != b'end %s\n' % rec[1]:
 
2020
                raise KnitCorrupt(self,
 
2021
                                  'unexpected version end line %r, wanted %r'
 
2022
                                  % (last_line, rec[1]))
1917
2023
        return rec, record_contents
1918
2024
 
1919
2025
    def _read_records_iter(self, records):
1922
2028
        The result will be returned in whatever is the fastest to read.
1923
2029
        Not by the order requested. Also, multiple requests for the same
1924
2030
        record will only yield 1 response.
 
2031
 
1925
2032
        :param records: A list of (key, access_memo) entries
1926
2033
        :return: Yields (key, contents, digest) in the order
1927
2034
                 read, not the order requested
1939
2046
        raw_data = self._access.get_raw_records(
1940
2047
            [index_memo for key, index_memo in needed_records])
1941
2048
 
1942
 
        for (key, index_memo), data in \
1943
 
                izip(iter(needed_records), raw_data):
 
2049
        for (key, index_memo), data in zip(needed_records, raw_data):
1944
2050
            content, digest = self._parse_record(key[-1], data)
1945
2051
            yield key, content, digest
1946
2052
 
1972
2078
        if len(records):
1973
2079
            # grab the disk data needed.
1974
2080
            needed_offsets = [index_memo for key, index_memo
1975
 
                                           in records]
 
2081
                              in records]
1976
2082
            raw_records = self._access.get_raw_records(needed_offsets)
1977
2083
 
1978
2084
        for key, index_memo in records:
1979
 
            data = raw_records.next()
 
2085
            data = next(raw_records)
1980
2086
            yield key, data
1981
2087
 
1982
2088
    def _record_to_data(self, key, digest, lines, dense_lines=None):
1985
2091
        :param key: The key of the record. Currently keys are always serialised
1986
2092
            using just the trailing component.
1987
2093
        :param dense_lines: The bytes of lines but in a denser form. For
1988
 
            instance, if lines is a list of 1000 bytestrings each ending in \n,
1989
 
            dense_lines may be a list with one line in it, containing all the
1990
 
            1000's lines and their \n's. Using dense_lines if it is already
1991
 
            known is a win because the string join to create bytes in this
1992
 
            function spends less time resizing the final string.
1993
 
        :return: (len, a StringIO instance with the raw data ready to read.)
 
2094
            instance, if lines is a list of 1000 bytestrings each ending in
 
2095
            \\n, dense_lines may be a list with one line in it, containing all
 
2096
            the 1000's lines and their \\n's. Using dense_lines if it is
 
2097
            already known is a win because the string join to create bytes in
 
2098
            this function spends less time resizing the final string.
 
2099
        :return: (len, chunked bytestring with compressed data)
1994
2100
        """
1995
 
        chunks = ["version %s %d %s\n" % (key[-1], len(lines), digest)]
 
2101
        chunks = [b"version %s %d %s\n" % (key[-1], len(lines), digest)]
1996
2102
        chunks.extend(dense_lines or lines)
1997
 
        chunks.append("end %s\n" % key[-1])
 
2103
        chunks.append(b"end " + key[-1] + b"\n")
1998
2104
        for chunk in chunks:
1999
 
            if type(chunk) is not str:
 
2105
            if not isinstance(chunk, bytes):
2000
2106
                raise AssertionError(
2001
2107
                    'data must be plain bytes was %s' % type(chunk))
2002
 
        if lines and lines[-1][-1] != '\n':
 
2108
        if lines and not lines[-1].endswith(b'\n'):
2003
2109
            raise ValueError('corrupt lines value %r' % lines)
2004
 
        compressed_bytes = tuned_gzip.chunks_to_gzip(chunks)
2005
 
        return len(compressed_bytes), compressed_bytes
 
2110
        compressed_chunks = tuned_gzip.chunks_to_gzip(chunks)
 
2111
        return sum(map(len, compressed_chunks)), compressed_chunks
2006
2112
 
2007
2113
    def _split_header(self, line):
2008
2114
        rec = line.split()
2015
2121
        """See VersionedFiles.keys."""
2016
2122
        if 'evil' in debug.debug_flags:
2017
2123
            trace.mutter_callsite(2, "keys scales with size of history")
2018
 
        sources = [self._index] + self._fallback_vfs
 
2124
        sources = [self._index] + self._immediate_fallback_vfs
2019
2125
        result = set()
2020
2126
        for source in sources:
2021
2127
            result.update(source.keys())
2033
2139
        # Note that _get_content is only called when the _ContentMapGenerator
2034
2140
        # has been constructed with just one key requested for reconstruction.
2035
2141
        if key in self.nonlocal_keys:
2036
 
            record = self.get_record_stream().next()
 
2142
            record = next(self.get_record_stream())
2037
2143
            # Create a content object on the fly
2038
 
            lines = osutils.chunks_to_lines(record.get_bytes_as('chunked'))
 
2144
            lines = record.get_bytes_as('lines')
2039
2145
            return PlainKnitContent(lines, record.key)
2040
2146
        else:
2041
2147
            # local keys we can ask for directly
2061
2167
 
2062
2168
        missing_keys = set(nonlocal_keys)
2063
2169
        # Read from remote versioned file instances and provide to our caller.
2064
 
        for source in self.vf._fallback_vfs:
 
2170
        for source in self.vf._immediate_fallback_vfs:
2065
2171
            if not missing_keys:
2066
2172
                break
2067
2173
            # Loop over fallback repositories asking them for texts - ignore
2068
2174
            # any missing from a particular fallback.
2069
2175
            for record in source.get_record_stream(missing_keys,
2070
 
                self._ordering, True):
 
2176
                                                   self._ordering, True):
2071
2177
                if record.storage_kind == 'absent':
2072
2178
                    # Not in thie particular stream, may be in one of the
2073
2179
                    # other fallback vfs objects.
2126
2232
                if component_id in self._contents_map:
2127
2233
                    content = self._contents_map[component_id]
2128
2234
                else:
2129
 
                    content, delta = self._factory.parse_record(key[-1],
2130
 
                        record, record_details, content,
 
2235
                    content, delta = self._factory.parse_record(
 
2236
                        key[-1], record, record_details, content,
2131
2237
                        copy_base_content=multiple_versions)
2132
2238
                    if multiple_versions:
2133
2239
                        self._contents_map[component_id] = content
2156
2262
        """
2157
2263
        lines = []
2158
2264
        # kind marker for dispatch on the far side,
2159
 
        lines.append('knit-delta-closure')
 
2265
        lines.append(b'knit-delta-closure')
2160
2266
        # Annotated or not
2161
2267
        if self.vf._factory.annotated:
2162
 
            lines.append('annotated')
 
2268
            lines.append(b'annotated')
2163
2269
        else:
2164
 
            lines.append('')
 
2270
            lines.append(b'')
2165
2271
        # then the list of keys
2166
 
        lines.append('\t'.join(['\x00'.join(key) for key in self.keys
2167
 
            if key not in self.nonlocal_keys]))
 
2272
        lines.append(b'\t'.join(b'\x00'.join(key) for key in self.keys
 
2273
                                if key not in self.nonlocal_keys))
2168
2274
        # then the _raw_record_map in serialised form:
2169
2275
        map_byte_list = []
2170
2276
        # for each item in the map:
2175
2281
        # one line with next ('' for None)
2176
2282
        # one line with byte count of the record bytes
2177
2283
        # the record bytes
2178
 
        for key, (record_bytes, (method, noeol), next) in \
2179
 
            self._raw_record_map.iteritems():
2180
 
            key_bytes = '\x00'.join(key)
 
2284
        for key, (record_bytes, (method, noeol), next) in viewitems(
 
2285
                self._raw_record_map):
 
2286
            key_bytes = b'\x00'.join(key)
2181
2287
            parents = self.global_map.get(key, None)
2182
2288
            if parents is None:
2183
 
                parent_bytes = 'None:'
 
2289
                parent_bytes = b'None:'
2184
2290
            else:
2185
 
                parent_bytes = '\t'.join('\x00'.join(key) for key in parents)
2186
 
            method_bytes = method
 
2291
                parent_bytes = b'\t'.join(b'\x00'.join(key) for key in parents)
 
2292
            method_bytes = method.encode('ascii')
2187
2293
            if noeol:
2188
 
                noeol_bytes = "T"
 
2294
                noeol_bytes = b"T"
2189
2295
            else:
2190
 
                noeol_bytes = "F"
 
2296
                noeol_bytes = b"F"
2191
2297
            if next:
2192
 
                next_bytes = '\x00'.join(next)
 
2298
                next_bytes = b'\x00'.join(next)
2193
2299
            else:
2194
 
                next_bytes = ''
2195
 
            map_byte_list.append('%s\n%s\n%s\n%s\n%s\n%d\n%s' % (
2196
 
                key_bytes, parent_bytes, method_bytes, noeol_bytes, next_bytes,
2197
 
                len(record_bytes), record_bytes))
2198
 
        map_bytes = ''.join(map_byte_list)
 
2300
                next_bytes = b''
 
2301
            map_byte_list.append(b'\n'.join(
 
2302
                [key_bytes, parent_bytes, method_bytes, noeol_bytes, next_bytes,
 
2303
                 b'%d' % len(record_bytes), record_bytes]))
 
2304
        map_bytes = b''.join(map_byte_list)
2199
2305
        lines.append(map_bytes)
2200
 
        bytes = '\n'.join(lines)
 
2306
        bytes = b'\n'.join(lines)
2201
2307
        return bytes
2202
2308
 
2203
2309
 
2205
2311
    """Content map generator reading from a VersionedFiles object."""
2206
2312
 
2207
2313
    def __init__(self, versioned_files, keys, nonlocal_keys=None,
2208
 
        global_map=None, raw_record_map=None, ordering='unordered'):
 
2314
                 global_map=None, raw_record_map=None, ordering='unordered'):
2209
2315
        """Create a _ContentMapGenerator.
2210
2316
 
2211
2317
        :param versioned_files: The versioned files that the texts are being
2240
2346
        self._record_map = None
2241
2347
        if raw_record_map is None:
2242
2348
            self._raw_record_map = self.vf._get_record_map_unparsed(keys,
2243
 
                allow_missing=True)
 
2349
                                                                    allow_missing=True)
2244
2350
        else:
2245
2351
            self._raw_record_map = raw_record_map
2246
2352
        # the factory for parsing records
2262
2368
        self.vf = KnitVersionedFiles(None, None)
2263
2369
        start = line_end
2264
2370
        # Annotated or not
2265
 
        line_end = bytes.find('\n', start)
 
2371
        line_end = bytes.find(b'\n', start)
2266
2372
        line = bytes[start:line_end]
2267
2373
        start = line_end + 1
2268
 
        if line == 'annotated':
 
2374
        if line == b'annotated':
2269
2375
            self._factory = KnitAnnotateFactory()
2270
2376
        else:
2271
2377
            self._factory = KnitPlainFactory()
2272
2378
        # list of keys to emit in get_record_stream
2273
 
        line_end = bytes.find('\n', start)
 
2379
        line_end = bytes.find(b'\n', start)
2274
2380
        line = bytes[start:line_end]
2275
2381
        start = line_end + 1
2276
2382
        self.keys = [
2277
 
            tuple(segment.split('\x00')) for segment in line.split('\t')
 
2383
            tuple(segment.split(b'\x00')) for segment in line.split(b'\t')
2278
2384
            if segment]
2279
2385
        # now a loop until the end. XXX: It would be nice if this was just a
2280
2386
        # bunch of the same records as get_record_stream(..., False) gives, but
2282
2388
        end = len(bytes)
2283
2389
        while start < end:
2284
2390
            # 1 line with key
2285
 
            line_end = bytes.find('\n', start)
2286
 
            key = tuple(bytes[start:line_end].split('\x00'))
 
2391
            line_end = bytes.find(b'\n', start)
 
2392
            key = tuple(bytes[start:line_end].split(b'\x00'))
2287
2393
            start = line_end + 1
2288
2394
            # 1 line with parents (None: for None, '' for ())
2289
 
            line_end = bytes.find('\n', start)
 
2395
            line_end = bytes.find(b'\n', start)
2290
2396
            line = bytes[start:line_end]
2291
 
            if line == 'None:':
 
2397
            if line == b'None:':
2292
2398
                parents = None
2293
2399
            else:
2294
2400
                parents = tuple(
2295
 
                    [tuple(segment.split('\x00')) for segment in line.split('\t')
2296
 
                     if segment])
 
2401
                    tuple(segment.split(b'\x00')) for segment in line.split(b'\t')
 
2402
                    if segment)
2297
2403
            self.global_map[key] = parents
2298
2404
            start = line_end + 1
2299
2405
            # one line with method
2300
 
            line_end = bytes.find('\n', start)
 
2406
            line_end = bytes.find(b'\n', start)
2301
2407
            line = bytes[start:line_end]
2302
 
            method = line
 
2408
            method = line.decode('ascii')
2303
2409
            start = line_end + 1
2304
2410
            # one line with noeol
2305
 
            line_end = bytes.find('\n', start)
 
2411
            line_end = bytes.find(b'\n', start)
2306
2412
            line = bytes[start:line_end]
2307
 
            noeol = line == "T"
 
2413
            noeol = line == b"T"
2308
2414
            start = line_end + 1
2309
 
            # one line with next ('' for None)
2310
 
            line_end = bytes.find('\n', start)
 
2415
            # one line with next (b'' for None)
 
2416
            line_end = bytes.find(b'\n', start)
2311
2417
            line = bytes[start:line_end]
2312
2418
            if not line:
2313
2419
                next = None
2314
2420
            else:
2315
 
                next = tuple(bytes[start:line_end].split('\x00'))
 
2421
                next = tuple(bytes[start:line_end].split(b'\x00'))
2316
2422
            start = line_end + 1
2317
2423
            # one line with byte count of the record bytes
2318
 
            line_end = bytes.find('\n', start)
 
2424
            line_end = bytes.find(b'\n', start)
2319
2425
            line = bytes[start:line_end]
2320
2426
            count = int(line)
2321
2427
            start = line_end + 1
2322
2428
            # the record bytes
2323
 
            record_bytes = bytes[start:start+count]
 
2429
            record_bytes = bytes[start:start + count]
2324
2430
            start = start + count
2325
2431
            # put it in the map
2326
2432
            self._raw_record_map[key] = (record_bytes, (method, noeol), next)
2395
2501
        ABI change with the C extension that reads .kndx files.
2396
2502
    """
2397
2503
 
2398
 
    HEADER = "# bzr knit index 8\n"
 
2504
    HEADER = b"# bzr knit index 8\n"
2399
2505
 
2400
2506
    def __init__(self, transport, mapper, get_scope, allow_writes, is_locked):
2401
2507
        """Create a _KndxIndex on transport using mapper."""
2440
2546
 
2441
2547
            try:
2442
2548
                for key, options, (_, pos, size), parents in path_keys:
 
2549
                    if not all(isinstance(option, bytes) for option in options):
 
2550
                        raise TypeError(options)
2443
2551
                    if parents is None:
2444
2552
                        # kndx indices cannot be parentless.
2445
2553
                        parents = ()
2446
 
                    line = "\n%s %s %s %s %s :" % (
2447
 
                        key[-1], ','.join(options), pos, size,
2448
 
                        self._dictionary_compress(parents))
2449
 
                    if type(line) is not str:
 
2554
                    line = b' '.join([
 
2555
                        b'\n'
 
2556
                        + key[-1], b','.join(options), b'%d' % pos, b'%d' % size,
 
2557
                        self._dictionary_compress(parents), b':'])
 
2558
                    if not isinstance(line, bytes):
2450
2559
                        raise AssertionError(
2451
2560
                            'data must be utf8 was %s' % type(line))
2452
2561
                    lines.append(line)
2453
2562
                    self._cache_key(key, options, pos, size, parents)
2454
2563
                if len(orig_history):
2455
 
                    self._transport.append_bytes(path, ''.join(lines))
 
2564
                    self._transport.append_bytes(path, b''.join(lines))
2456
2565
                else:
2457
2566
                    self._init_index(path, lines)
2458
2567
            except:
2496
2605
        else:
2497
2606
            index = cache[version_id][5]
2498
2607
        cache[version_id] = (version_id,
2499
 
                                   options,
2500
 
                                   pos,
2501
 
                                   size,
2502
 
                                   parents,
2503
 
                                   index)
 
2608
                             options,
 
2609
                             pos,
 
2610
                             size,
 
2611
                             parents,
 
2612
                             index)
2504
2613
 
2505
2614
    def check_header(self, fp):
2506
2615
        line = fp.readline()
2507
 
        if line == '':
 
2616
        if line == b'':
2508
2617
            # An empty file can actually be treated as though the file doesn't
2509
2618
            # exist yet.
2510
2619
            raise errors.NoSuchFile(self)
2549
2658
        result = {}
2550
2659
        for key in keys:
2551
2660
            if key not in parent_map:
2552
 
                continue # Ghost
 
2661
                continue  # Ghost
2553
2662
            method = self.get_method(key)
 
2663
            if not isinstance(method, str):
 
2664
                raise TypeError(method)
2554
2665
            parents = parent_map[key]
2555
2666
            if method == 'fulltext':
2556
2667
                compression_parent = None
2557
2668
            else:
2558
2669
                compression_parent = parents[0]
2559
 
            noeol = 'no-eol' in self.get_options(key)
 
2670
            noeol = b'no-eol' in self.get_options(key)
2560
2671
            index_memo = self.get_position(key)
2561
2672
            result[key] = (index_memo, compression_parent,
2562
 
                                  parents, (method, noeol))
 
2673
                           parents, (method, noeol))
2563
2674
        return result
2564
2675
 
2565
2676
    def get_method(self, key):
2566
2677
        """Return compression method of specified key."""
2567
2678
        options = self.get_options(key)
2568
 
        if 'fulltext' in options:
 
2679
        if b'fulltext' in options:
2569
2680
            return 'fulltext'
2570
 
        elif 'line-delta' in options:
 
2681
        elif b'line-delta' in options:
2571
2682
            return 'line-delta'
2572
2683
        else:
2573
 
            raise errors.KnitIndexUnknownMethod(self, options)
 
2684
            raise KnitIndexUnknownMethod(self, options)
2574
2685
 
2575
2686
    def get_options(self, key):
2576
2687
        """Return a list representing options.
2608
2719
                                     for suffix in suffix_parents])
2609
2720
                parent_map[key] = parent_keys
2610
2721
                pending_keys.extend([p for p in parent_keys
2611
 
                                        if p not in parent_map])
 
2722
                                     if p not in parent_map])
2612
2723
        return parent_map, missing_keys
2613
2724
 
2614
2725
    def get_parent_map(self, keys):
2632
2743
                pass
2633
2744
            else:
2634
2745
                result[key] = tuple(prefix + (suffix,) for
2635
 
                    suffix in suffix_parents)
 
2746
                                    suffix in suffix_parents)
2636
2747
        return result
2637
2748
 
2638
2749
    def get_position(self, key):
2646
2757
        entry = self._kndx_cache[prefix][0][suffix]
2647
2758
        return key, entry[2], entry[3]
2648
2759
 
2649
 
    has_key = _mod_index._has_key_from_parent_map
 
2760
    __contains__ = _mod_index._has_key_from_parent_map
2650
2761
 
2651
2762
    def _init_index(self, path, extra_lines=[]):
2652
2763
        """Initialize an index."""
2653
 
        sio = StringIO()
 
2764
        sio = BytesIO()
2654
2765
        sio.write(self.HEADER)
2655
2766
        sio.writelines(extra_lines)
2656
2767
        sio.seek(0)
2657
2768
        self._transport.put_file_non_atomic(path, sio,
2658
 
                            create_parent_dir=True)
2659
 
                           # self._create_parent_dir)
2660
 
                           # mode=self._file_mode,
2661
 
                           # dir_mode=self._dir_mode)
 
2769
                                            create_parent_dir=True)
 
2770
        # self._create_parent_dir)
 
2771
        # mode=self._file_mode,
 
2772
        # dir_mode=self._dir_mode)
2662
2773
 
2663
2774
    def keys(self):
2664
2775
        """Get all the keys in the collection.
2668
2779
        result = set()
2669
2780
        # Identify all key prefixes.
2670
2781
        # XXX: A bit hacky, needs polish.
2671
 
        if type(self._mapper) is ConstantMapper:
 
2782
        if isinstance(self._mapper, ConstantMapper):
2672
2783
            prefixes = [()]
2673
2784
        else:
2674
2785
            relpaths = set()
2693
2804
                self._filename = prefix
2694
2805
                try:
2695
2806
                    path = self._mapper.map(prefix) + '.kndx'
2696
 
                    fp = self._transport.get(path)
2697
 
                    try:
 
2807
                    with self._transport.get(path) as fp:
2698
2808
                        # _load_data may raise NoSuchFile if the target knit is
2699
2809
                        # completely empty.
2700
2810
                        _load_data(self, fp)
2701
 
                    finally:
2702
 
                        fp.close()
2703
2811
                    self._kndx_cache[prefix] = (self._cache, self._history)
2704
2812
                    del self._cache
2705
2813
                    del self._filename
2706
2814
                    del self._history
2707
2815
                except NoSuchFile:
2708
2816
                    self._kndx_cache[prefix] = ({}, [])
2709
 
                    if type(self._mapper) is ConstantMapper:
 
2817
                    if isinstance(self._mapper, ConstantMapper):
2710
2818
                        # preserve behaviour for revisions.kndx etc.
2711
2819
                        self._init_index(path)
2712
2820
                    del self._cache
2732
2840
            '.' prefix.
2733
2841
        """
2734
2842
        if not keys:
2735
 
            return ''
 
2843
            return b''
2736
2844
        result_list = []
2737
2845
        prefix = keys[0][:-1]
2738
2846
        cache = self._kndx_cache[prefix][0]
2742
2850
                raise ValueError("mismatched prefixes for %r" % keys)
2743
2851
            if key[-1] in cache:
2744
2852
                # -- inlined lookup() --
2745
 
                result_list.append(str(cache[key[-1]][5]))
 
2853
                result_list.append(b'%d' % cache[key[-1]][5])
2746
2854
                # -- end lookup () --
2747
2855
            else:
2748
 
                result_list.append('.' + key[-1])
2749
 
        return ' '.join(result_list)
 
2856
                result_list.append(b'.' + key[-1])
 
2857
        return b' '.join(result_list)
2750
2858
 
2751
2859
    def _reset_cache(self):
2752
2860
        # Possibly this should be a LRU cache. A dictionary from key_prefix to
2783
2891
 
2784
2892
    def _split_key(self, key):
2785
2893
        """Split key into a prefix and suffix."""
 
2894
        # GZ 2018-07-03: This is intentionally either a sequence or bytes?
 
2895
        if isinstance(key, bytes):
 
2896
            return key[:-1], key[-1:]
2786
2897
        return key[:-1], key[-1]
2787
2898
 
2788
2899
 
2789
 
class _KeyRefs(object):
2790
 
 
2791
 
    def __init__(self, track_new_keys=False):
2792
 
        # dict mapping 'key' to 'set of keys referring to that key'
2793
 
        self.refs = {}
2794
 
        if track_new_keys:
2795
 
            # set remembering all new keys
2796
 
            self.new_keys = set()
2797
 
        else:
2798
 
            self.new_keys = None
2799
 
 
2800
 
    def clear(self):
2801
 
        if self.refs:
2802
 
            self.refs.clear()
2803
 
        if self.new_keys:
2804
 
            self.new_keys.clear()
2805
 
 
2806
 
    def add_references(self, key, refs):
2807
 
        # Record the new references
2808
 
        for referenced in refs:
2809
 
            try:
2810
 
                needed_by = self.refs[referenced]
2811
 
            except KeyError:
2812
 
                needed_by = self.refs[referenced] = set()
2813
 
            needed_by.add(key)
2814
 
        # Discard references satisfied by the new key
2815
 
        self.add_key(key)
2816
 
 
2817
 
    def get_new_keys(self):
2818
 
        return self.new_keys
2819
 
    
2820
 
    def get_unsatisfied_refs(self):
2821
 
        return self.refs.iterkeys()
2822
 
 
2823
 
    def _satisfy_refs_for_key(self, key):
2824
 
        try:
2825
 
            del self.refs[key]
2826
 
        except KeyError:
2827
 
            # No keys depended on this key.  That's ok.
2828
 
            pass
2829
 
 
2830
 
    def add_key(self, key):
2831
 
        # satisfy refs for key, and remember that we've seen this key.
2832
 
        self._satisfy_refs_for_key(key)
2833
 
        if self.new_keys is not None:
2834
 
            self.new_keys.add(key)
2835
 
 
2836
 
    def satisfy_refs_for_keys(self, keys):
2837
 
        for key in keys:
2838
 
            self._satisfy_refs_for_key(key)
2839
 
 
2840
 
    def get_referrers(self):
2841
 
        result = set()
2842
 
        for referrers in self.refs.itervalues():
2843
 
            result.update(referrers)
2844
 
        return result
2845
 
 
2846
 
 
2847
2900
class _KnitGraphIndex(object):
2848
2901
    """A KnitVersionedFiles index layered on GraphIndex."""
2849
2902
 
2850
2903
    def __init__(self, graph_index, is_locked, deltas=False, parents=True,
2851
 
        add_callback=None, track_external_parent_refs=False):
 
2904
                 add_callback=None, track_external_parent_refs=False):
2852
2905
        """Construct a KnitGraphIndex on a graph_index.
2853
2906
 
2854
 
        :param graph_index: An implementation of bzrlib.index.GraphIndex.
 
2907
        :param graph_index: An implementation of breezy.index.GraphIndex.
2855
2908
        :param is_locked: A callback to check whether the object should answer
2856
2909
            queries.
2857
2910
        :param deltas: Allow delta-compressed records.
2874
2927
            # XXX: TODO: Delta tree and parent graph should be conceptually
2875
2928
            # separate.
2876
2929
            raise KnitCorrupt(self, "Cannot do delta compression without "
2877
 
                "parent tracking.")
 
2930
                              "parent tracking.")
2878
2931
        self.has_graph = parents
2879
2932
        self._is_locked = is_locked
2880
2933
        self._missing_compression_parents = set()
2887
2940
        return "%s(%r)" % (self.__class__.__name__, self._graph_index)
2888
2941
 
2889
2942
    def add_records(self, records, random_id=False,
2890
 
        missing_compression_parents=False):
 
2943
                    missing_compression_parents=False):
2891
2944
        """Add multiple records to the index.
2892
2945
 
2893
2946
        This function does not insert data into the Immutable GraphIndex
2918
2971
                if key_dependencies is not None:
2919
2972
                    key_dependencies.add_references(key, parents)
2920
2973
            index, pos, size = access_memo
2921
 
            if 'no-eol' in options:
2922
 
                value = 'N'
 
2974
            if b'no-eol' in options:
 
2975
                value = b'N'
2923
2976
            else:
2924
 
                value = ' '
2925
 
            value += "%d %d" % (pos, size)
 
2977
                value = b' '
 
2978
            value += b"%d %d" % (pos, size)
2926
2979
            if not self._deltas:
2927
 
                if 'line-delta' in options:
2928
 
                    raise KnitCorrupt(self, "attempt to add line-delta in non-delta knit")
 
2980
                if b'line-delta' in options:
 
2981
                    raise KnitCorrupt(
 
2982
                        self, "attempt to add line-delta in non-delta knit")
2929
2983
            if self._parents:
2930
2984
                if self._deltas:
2931
 
                    if 'line-delta' in options:
 
2985
                    if b'line-delta' in options:
2932
2986
                        node_refs = (parents, (parents[0],))
2933
2987
                        if missing_compression_parents:
2934
2988
                            compression_parents.add(parents[0])
2939
2993
            else:
2940
2994
                if parents:
2941
2995
                    raise KnitCorrupt(self, "attempt to add node with parents "
2942
 
                        "in parentless index.")
 
2996
                                      "in parentless index.")
2943
2997
                node_refs = ()
2944
2998
            keys[key] = (value, node_refs)
2945
2999
        # check for dups
2950
3004
                # Sometimes these are passed as a list rather than a tuple
2951
3005
                passed = static_tuple.as_tuples(keys[key])
2952
3006
                passed_parents = passed[1][:1]
2953
 
                if (value[0] != keys[key][0][0] or
2954
 
                    parents != passed_parents):
 
3007
                if (value[0:1] != keys[key][0][0:1]
 
3008
                        or parents != passed_parents):
2955
3009
                    node_refs = static_tuple.as_tuples(node_refs)
2956
3010
                    raise KnitCorrupt(self, "inconsistent details in add_records"
2957
 
                        ": %s %s" % ((value, node_refs), passed))
 
3011
                                      ": %s %s" % ((value, node_refs), passed))
2958
3012
                del keys[key]
2959
3013
        result = []
2960
3014
        if self._parents:
2961
 
            for key, (value, node_refs) in keys.iteritems():
 
3015
            for key, (value, node_refs) in viewitems(keys):
2962
3016
                result.append((key, value, node_refs))
2963
3017
        else:
2964
 
            for key, (value, node_refs) in keys.iteritems():
 
3018
            for key, (value, node_refs) in viewitems(keys):
2965
3019
                result.append((key, value))
2966
3020
        self._add_callback(result)
2967
3021
        if missing_compression_parents:
3065
3119
                compression_parent_key = None
3066
3120
            else:
3067
3121
                compression_parent_key = self._compression_parent(entry)
3068
 
            noeol = (entry[2][0] == 'N')
 
3122
            noeol = (entry[2][0:1] == b'N')
3069
3123
            if compression_parent_key:
3070
3124
                method = 'line-delta'
3071
3125
            else:
3072
3126
                method = 'fulltext'
3073
3127
            result[key] = (self._node_to_position(entry),
3074
 
                                  compression_parent_key, parents,
3075
 
                                  (method, noeol))
 
3128
                           compression_parent_key, parents,
 
3129
                           (method, noeol))
3076
3130
        return result
3077
3131
 
3078
3132
    def _get_entries(self, keys, check_present=False):
3120
3174
        e.g. ['foo', 'bar']
3121
3175
        """
3122
3176
        node = self._get_node(key)
3123
 
        options = [self._get_method(node)]
3124
 
        if node[2][0] == 'N':
3125
 
            options.append('no-eol')
 
3177
        options = [self._get_method(node).encode('ascii')]
 
3178
        if node[2][0:1] == b'N':
 
3179
            options.append(b'no-eol')
3126
3180
        return options
3127
3181
 
3128
3182
    def find_ancestry(self, keys):
3156
3210
        node = self._get_node(key)
3157
3211
        return self._node_to_position(node)
3158
3212
 
3159
 
    has_key = _mod_index._has_key_from_parent_map
 
3213
    __contains__ = _mod_index._has_key_from_parent_map
3160
3214
 
3161
3215
    def keys(self):
3162
3216
        """Get all the keys in the collection.
3170
3224
 
3171
3225
    def _node_to_position(self, node):
3172
3226
        """Convert an index value to position details."""
3173
 
        bits = node[2][1:].split(' ')
 
3227
        bits = node[2][1:].split(b' ')
3174
3228
        return node[0], int(bits[0]), int(bits[1])
3175
3229
 
3176
3230
    def _sort_keys_by_io(self, keys, positions):
3208
3262
        self._transport = transport
3209
3263
        self._mapper = mapper
3210
3264
 
 
3265
    def add_raw_record(self, key, size, raw_data):
 
3266
        """Add raw knit bytes to a storage area.
 
3267
 
 
3268
        The data is spooled to the container writer in one bytes-record per
 
3269
        raw data item.
 
3270
 
 
3271
        :param key: The key of the raw data segment
 
3272
        :param size: The size of the raw data segment
 
3273
        :param raw_data: A chunked bytestring containing the data.
 
3274
        :return: opaque index memo to retrieve the record later.
 
3275
            For _KnitKeyAccess the memo is (key, pos, length), where the key is
 
3276
            the record key.
 
3277
        """
 
3278
        path = self._mapper.map(key)
 
3279
        try:
 
3280
            base = self._transport.append_bytes(path + '.knit', b''.join(raw_data))
 
3281
        except errors.NoSuchFile:
 
3282
            self._transport.mkdir(osutils.dirname(path))
 
3283
            base = self._transport.append_bytes(path + '.knit', b''.join(raw_data))
 
3284
        # if base == 0:
 
3285
        # chmod.
 
3286
        return (key, base, size)
 
3287
 
3211
3288
    def add_raw_records(self, key_sizes, raw_data):
3212
3289
        """Add raw knit bytes to a storage area.
3213
3290
 
3216
3293
 
3217
3294
        :param sizes: An iterable of tuples containing the key and size of each
3218
3295
            raw data segment.
3219
 
        :param raw_data: A bytestring containing the data.
 
3296
        :param raw_data: A chunked bytestring containing the data.
3220
3297
        :return: A list of memos to retrieve the record later. Each memo is an
3221
3298
            opaque index memo. For _KnitKeyAccess the memo is (key, pos,
3222
3299
            length), where the key is the record key.
3223
3300
        """
3224
 
        if type(raw_data) is not str:
 
3301
        raw_data = b''.join(raw_data)
 
3302
        if not isinstance(raw_data, bytes):
3225
3303
            raise AssertionError(
3226
3304
                'data must be plain bytes was %s' % type(raw_data))
3227
3305
        result = []
3230
3308
        # append() is relatively expensive by grouping the writes to each key
3231
3309
        # prefix.
3232
3310
        for key, size in key_sizes:
3233
 
            path = self._mapper.map(key)
3234
 
            try:
3235
 
                base = self._transport.append_bytes(path + '.knit',
3236
 
                    raw_data[offset:offset+size])
3237
 
            except errors.NoSuchFile:
3238
 
                self._transport.mkdir(osutils.dirname(path))
3239
 
                base = self._transport.append_bytes(path + '.knit',
3240
 
                    raw_data[offset:offset+size])
3241
 
            # if base == 0:
3242
 
            # chmod.
 
3311
            record_bytes = [raw_data[offset:offset + size]]
 
3312
            result.append(self.add_raw_record(key, size, record_bytes))
3243
3313
            offset += size
3244
 
            result.append((key, base, size))
3245
3314
        return result
3246
3315
 
3247
3316
    def flush(self):
3248
3317
        """Flush pending writes on this access object.
3249
 
        
 
3318
 
3250
3319
        For .knit files this is a no-op.
3251
3320
        """
3252
3321
        pass
3278
3347
                yield data
3279
3348
 
3280
3349
 
3281
 
class _DirectPackAccess(object):
3282
 
    """Access to data in one or more packs with less translation."""
3283
 
 
3284
 
    def __init__(self, index_to_packs, reload_func=None, flush_func=None):
3285
 
        """Create a _DirectPackAccess object.
3286
 
 
3287
 
        :param index_to_packs: A dict mapping index objects to the transport
3288
 
            and file names for obtaining data.
3289
 
        :param reload_func: A function to call if we determine that the pack
3290
 
            files have moved and we need to reload our caches. See
3291
 
            bzrlib.repo_fmt.pack_repo.AggregateIndex for more details.
3292
 
        """
3293
 
        self._container_writer = None
3294
 
        self._write_index = None
3295
 
        self._indices = index_to_packs
3296
 
        self._reload_func = reload_func
3297
 
        self._flush_func = flush_func
3298
 
 
3299
 
    def add_raw_records(self, key_sizes, raw_data):
3300
 
        """Add raw knit bytes to a storage area.
3301
 
 
3302
 
        The data is spooled to the container writer in one bytes-record per
3303
 
        raw data item.
3304
 
 
3305
 
        :param sizes: An iterable of tuples containing the key and size of each
3306
 
            raw data segment.
3307
 
        :param raw_data: A bytestring containing the data.
3308
 
        :return: A list of memos to retrieve the record later. Each memo is an
3309
 
            opaque index memo. For _DirectPackAccess the memo is (index, pos,
3310
 
            length), where the index field is the write_index object supplied
3311
 
            to the PackAccess object.
3312
 
        """
3313
 
        if type(raw_data) is not str:
3314
 
            raise AssertionError(
3315
 
                'data must be plain bytes was %s' % type(raw_data))
3316
 
        result = []
3317
 
        offset = 0
3318
 
        for key, size in key_sizes:
3319
 
            p_offset, p_length = self._container_writer.add_bytes_record(
3320
 
                raw_data[offset:offset+size], [])
3321
 
            offset += size
3322
 
            result.append((self._write_index, p_offset, p_length))
3323
 
        return result
3324
 
 
3325
 
    def flush(self):
3326
 
        """Flush pending writes on this access object.
3327
 
 
3328
 
        This will flush any buffered writes to a NewPack.
3329
 
        """
3330
 
        if self._flush_func is not None:
3331
 
            self._flush_func()
3332
 
            
3333
 
    def get_raw_records(self, memos_for_retrieval):
3334
 
        """Get the raw bytes for a records.
3335
 
 
3336
 
        :param memos_for_retrieval: An iterable containing the (index, pos,
3337
 
            length) memo for retrieving the bytes. The Pack access method
3338
 
            looks up the pack to use for a given record in its index_to_pack
3339
 
            map.
3340
 
        :return: An iterator over the bytes of the records.
3341
 
        """
3342
 
        # first pass, group into same-index requests
3343
 
        request_lists = []
3344
 
        current_index = None
3345
 
        for (index, offset, length) in memos_for_retrieval:
3346
 
            if current_index == index:
3347
 
                current_list.append((offset, length))
3348
 
            else:
3349
 
                if current_index is not None:
3350
 
                    request_lists.append((current_index, current_list))
3351
 
                current_index = index
3352
 
                current_list = [(offset, length)]
3353
 
        # handle the last entry
3354
 
        if current_index is not None:
3355
 
            request_lists.append((current_index, current_list))
3356
 
        for index, offsets in request_lists:
3357
 
            try:
3358
 
                transport, path = self._indices[index]
3359
 
            except KeyError:
3360
 
                # A KeyError here indicates that someone has triggered an index
3361
 
                # reload, and this index has gone missing, we need to start
3362
 
                # over.
3363
 
                if self._reload_func is None:
3364
 
                    # If we don't have a _reload_func there is nothing that can
3365
 
                    # be done
3366
 
                    raise
3367
 
                raise errors.RetryWithNewPacks(index,
3368
 
                                               reload_occurred=True,
3369
 
                                               exc_info=sys.exc_info())
3370
 
            try:
3371
 
                reader = pack.make_readv_reader(transport, path, offsets)
3372
 
                for names, read_func in reader.iter_records():
3373
 
                    yield read_func(None)
3374
 
            except errors.NoSuchFile:
3375
 
                # A NoSuchFile error indicates that a pack file has gone
3376
 
                # missing on disk, we need to trigger a reload, and start over.
3377
 
                if self._reload_func is None:
3378
 
                    raise
3379
 
                raise errors.RetryWithNewPacks(transport.abspath(path),
3380
 
                                               reload_occurred=False,
3381
 
                                               exc_info=sys.exc_info())
3382
 
 
3383
 
    def set_writer(self, writer, index, transport_packname):
3384
 
        """Set a writer to use for adding data."""
3385
 
        if index is not None:
3386
 
            self._indices[index] = transport_packname
3387
 
        self._container_writer = writer
3388
 
        self._write_index = index
3389
 
 
3390
 
    def reload_or_raise(self, retry_exc):
3391
 
        """Try calling the reload function, or re-raise the original exception.
3392
 
 
3393
 
        This should be called after _DirectPackAccess raises a
3394
 
        RetryWithNewPacks exception. This function will handle the common logic
3395
 
        of determining when the error is fatal versus being temporary.
3396
 
        It will also make sure that the original exception is raised, rather
3397
 
        than the RetryWithNewPacks exception.
3398
 
 
3399
 
        If this function returns, then the calling function should retry
3400
 
        whatever operation was being performed. Otherwise an exception will
3401
 
        be raised.
3402
 
 
3403
 
        :param retry_exc: A RetryWithNewPacks exception.
3404
 
        """
3405
 
        is_error = False
3406
 
        if self._reload_func is None:
3407
 
            is_error = True
3408
 
        elif not self._reload_func():
3409
 
            # The reload claimed that nothing changed
3410
 
            if not retry_exc.reload_occurred:
3411
 
                # If there wasn't an earlier reload, then we really were
3412
 
                # expecting to find changes. We didn't find them, so this is a
3413
 
                # hard error
3414
 
                is_error = True
3415
 
        if is_error:
3416
 
            exc_class, exc_value, exc_traceback = retry_exc.exc_info
3417
 
            raise exc_class, exc_value, exc_traceback
3418
 
 
3419
 
 
3420
 
# Deprecated, use PatienceSequenceMatcher instead
3421
 
KnitSequenceMatcher = patiencediff.PatienceSequenceMatcher
3422
 
 
3423
 
 
3424
3350
def annotate_knit(knit, revision_id):
3425
3351
    """Annotate a knit with no cached annotations.
3426
3352
 
3469
3395
            passing to read_records_iter to start reading in the raw data from
3470
3396
            the pack file.
3471
3397
        """
3472
 
        pending = set([key])
 
3398
        pending = {key}
3473
3399
        records = []
3474
3400
        ann_keys = set()
3475
3401
        self._num_needed_children[key] = 1
3480
3406
            self._all_build_details.update(build_details)
3481
3407
            # new_nodes = self._vf._index._get_entries(this_iteration)
3482
3408
            pending = set()
3483
 
            for key, details in build_details.iteritems():
 
3409
            for key, details in viewitems(build_details):
3484
3410
                (index_memo, compression_parent, parent_keys,
3485
3411
                 record_details) = details
3486
3412
                self._parent_map[key] = parent_keys
3488
3414
                records.append((key, index_memo))
3489
3415
                # Do we actually need to check _annotated_lines?
3490
3416
                pending.update([p for p in parent_keys
3491
 
                                   if p not in self._all_build_details])
 
3417
                                if p not in self._all_build_details])
3492
3418
                if parent_keys:
3493
3419
                    for parent_key in parent_keys:
3494
3420
                        if parent_key in self._num_needed_children:
3501
3427
                    else:
3502
3428
                        self._num_compression_children[compression_parent] = 1
3503
3429
 
3504
 
            missing_versions = this_iteration.difference(build_details.keys())
 
3430
            missing_versions = this_iteration.difference(build_details)
3505
3431
            if missing_versions:
3506
3432
                for key in missing_versions:
3507
3433
                    if key in self._parent_map and key in self._text_cache:
3515
3441
                            else:
3516
3442
                                self._num_needed_children[parent_key] = 1
3517
3443
                        pending.update([p for p in parent_keys
3518
 
                                           if p not in self._all_build_details])
 
3444
                                        if p not in self._all_build_details])
3519
3445
                    else:
3520
3446
                        raise errors.RevisionNotPresent(key, self._vf)
3521
3447
        # Generally we will want to read the records in reverse order, because
3524
3450
        return records, ann_keys
3525
3451
 
3526
3452
    def _get_needed_texts(self, key, pb=None):
3527
 
        # if True or len(self._vf._fallback_vfs) > 0:
3528
 
        if len(self._vf._fallback_vfs) > 0:
 
3453
        # if True or len(self._vf._immediate_fallback_vfs) > 0:
 
3454
        if len(self._vf._immediate_fallback_vfs) > 0:
3529
3455
            # If we have fallbacks, go to the generic path
3530
3456
            for v in annotate.Annotator._get_needed_texts(self, key, pb=pb):
3531
3457
                yield v
3534
3460
            try:
3535
3461
                records, ann_keys = self._get_build_graph(key)
3536
3462
                for idx, (sub_key, text, num_lines) in enumerate(
3537
 
                                                self._extract_texts(records)):
 
3463
                        self._extract_texts(records)):
3538
3464
                    if pb is not None:
3539
 
                        pb.update('annotating', idx, len(records))
 
3465
                        pb.update(gettext('annotating'), idx, len(records))
3540
3466
                    yield sub_key, text, num_lines
3541
3467
                for sub_key in ann_keys:
3542
3468
                    text = self._text_cache[sub_key]
3543
 
                    num_lines = len(text) # bad assumption
 
3469
                    num_lines = len(text)  # bad assumption
3544
3470
                    yield sub_key, text, num_lines
3545
3471
                return
3546
 
            except errors.RetryWithNewPacks, e:
 
3472
            except errors.RetryWithNewPacks as e:
3547
3473
                self._vf._access.reload_or_raise(e)
3548
3474
                # The cached build_details are no longer valid
3549
3475
                self._all_build_details.clear()
3550
3476
 
3551
3477
    def _cache_delta_blocks(self, key, compression_parent, delta, lines):
3552
3478
        parent_lines = self._text_cache[compression_parent]
3553
 
        blocks = list(KnitContent.get_line_delta_blocks(delta, parent_lines, lines))
 
3479
        blocks = list(KnitContent.get_line_delta_blocks(
 
3480
            delta, parent_lines, lines))
3554
3481
        self._matching_blocks[(key, compression_parent)] = blocks
3555
3482
 
3556
3483
    def _expand_record(self, key, parent_keys, compression_parent, record,
3609
3536
            parent_annotations = self._annotations_cache[parent_key]
3610
3537
            return parent_annotations, blocks
3611
3538
        return annotate.Annotator._get_parent_annotations_and_matches(self,
3612
 
            key, text, parent_key)
 
3539
                                                                      key, text, parent_key)
3613
3540
 
3614
3541
    def _process_pending(self, key):
3615
3542
        """The content for 'key' was just processed.
3646
3573
                # Note that if there are multiple parents, we need to wait
3647
3574
                # for all of them.
3648
3575
                self._pending_annotation.setdefault(parent_key,
3649
 
                    []).append((key, parent_keys))
 
3576
                                                    []).append((key, parent_keys))
3650
3577
                return False
3651
3578
        return True
3652
3579
 
3707
3634
                    yield key, lines, len(lines)
3708
3635
                    to_process.extend(self._process_pending(key))
3709
3636
 
 
3637
 
3710
3638
try:
3711
 
    from bzrlib._knit_load_data_pyx import _load_data_c as _load_data
3712
 
except ImportError, e:
 
3639
    from ._knit_load_data_pyx import _load_data_c as _load_data
 
3640
except ImportError as e:
3713
3641
    osutils.failed_to_load_extension(e)
3714
 
    from bzrlib._knit_load_data_py import _load_data_py as _load_data
 
3642
    from ._knit_load_data_py import _load_data_py as _load_data