/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/knit.py

  • Committer: Robert Collins
  • Date: 2010-05-06 23:41:35 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506234135-yivbzczw1sejxnxc
Lock methods on ``Tree``, ``Branch`` and ``Repository`` are now
expected to return an object which can be used to unlock them. This reduces
duplicate code when using cleanups. The previous 'tokens's returned by
``Branch.lock_write`` and ``Repository.lock_write`` are now attributes
on the result of the lock_write. ``repository.RepositoryWriteLockResult``
and ``branch.BranchWriteLockResult`` document this. (Robert Collins)

``log._get_info_for_log_files`` now takes an add_cleanup callable.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

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