/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-06-28 23:13:22 UTC
  • mto: (7490.40.37 work)
  • mto: This revision was merged to the branch mainline in revision 7519.
  • Revision ID: jelmer@jelmer.uk-20200628231322-6h0upmtsh8ec7504
some refactoring.

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