/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/versionedfile.py

  • Committer: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

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
#
 
3
# Authors:
 
4
#   Johan Rydberg <jrydberg@gnu.org>
2
5
#
3
6
# This program is free software; you can redistribute it and/or modify
4
7
# it under the terms of the GNU General Public License as published by
16
19
 
17
20
"""Versioned text file storage api."""
18
21
 
19
 
from __future__ import absolute_import
20
 
 
21
22
from copy import copy
22
 
import itertools
 
23
from cStringIO import StringIO
23
24
import os
24
25
import struct
25
26
from zlib import adler32
26
27
 
27
 
from ..lazy_import import lazy_import
 
28
from bzrlib.lazy_import import lazy_import
28
29
lazy_import(globals(), """
29
 
from breezy import (
 
30
import urllib
 
31
 
 
32
from bzrlib import (
30
33
    annotate,
31
 
    bencode,
 
34
    errors,
32
35
    graph as _mod_graph,
 
36
    groupcompress,
 
37
    index,
 
38
    knit,
33
39
    osutils,
34
40
    multiparent,
35
41
    tsort,
36
42
    revision,
37
 
    urlutils,
38
 
    )
39
 
from breezy.bzr import (
40
 
    groupcompress,
41
 
    index,
42
 
    knit,
43
 
    )
 
43
    ui,
 
44
    )
 
45
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
 
46
from bzrlib.transport.memory import MemoryTransport
44
47
""")
45
 
from .. import (
46
 
    errors,
47
 
    )
48
 
from ..registry import Registry
49
 
from ..sixish import (
50
 
    BytesIO,
51
 
    viewitems,
52
 
    viewvalues,
53
 
    zip,
54
 
    )
55
 
from ..textmerge import TextMerge
 
48
from bzrlib.registry import Registry
 
49
from bzrlib.symbol_versioning import *
 
50
from bzrlib.textmerge import TextMerge
 
51
from bzrlib import bencode
56
52
 
57
53
 
58
54
adapter_registry = Registry()
 
55
adapter_registry.register_lazy(('knit-delta-gz', 'fulltext'), 'bzrlib.knit',
 
56
    'DeltaPlainToFullText')
 
57
adapter_registry.register_lazy(('knit-ft-gz', 'fulltext'), 'bzrlib.knit',
 
58
    'FTPlainToFullText')
59
59
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'knit-delta-gz'),
60
 
                               'breezy.bzr.knit', 'DeltaAnnotatedToUnannotated')
 
60
    'bzrlib.knit', 'DeltaAnnotatedToUnannotated')
 
61
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'fulltext'),
 
62
    'bzrlib.knit', 'DeltaAnnotatedToFullText')
61
63
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'knit-ft-gz'),
62
 
                               'breezy.bzr.knit', 'FTAnnotatedToUnannotated')
63
 
for target_storage_kind in ('fulltext', 'chunked', 'lines'):
64
 
    adapter_registry.register_lazy(('knit-delta-gz', target_storage_kind), 'breezy.bzr.knit',
65
 
                                   'DeltaPlainToFullText')
66
 
    adapter_registry.register_lazy(('knit-ft-gz', target_storage_kind), 'breezy.bzr.knit',
67
 
                                   'FTPlainToFullText')
68
 
    adapter_registry.register_lazy(('knit-annotated-ft-gz', target_storage_kind),
69
 
                                   'breezy.bzr.knit', 'FTAnnotatedToFullText')
70
 
    adapter_registry.register_lazy(('knit-annotated-delta-gz', target_storage_kind),
71
 
                                   'breezy.bzr.knit', 'DeltaAnnotatedToFullText')
 
64
    'bzrlib.knit', 'FTAnnotatedToUnannotated')
 
65
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'fulltext'),
 
66
    'bzrlib.knit', 'FTAnnotatedToFullText')
 
67
# adapter_registry.register_lazy(('knit-annotated-ft-gz', 'chunked'),
 
68
#     'bzrlib.knit', 'FTAnnotatedToChunked')
72
69
 
73
70
 
74
71
class ContentFactory(object):
75
72
    """Abstract interface for insertion and retrieval from a VersionedFile.
76
73
 
77
74
    :ivar sha1: None, or the sha1 of the content fulltext.
78
 
    :ivar size: None, or the size of the content fulltext.
79
75
    :ivar storage_kind: The native storage kind of this factory. One of
80
76
        'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
81
77
        'knit-delta', 'fulltext', 'knit-annotated-ft-gz',
90
86
    def __init__(self):
91
87
        """Create a ContentFactory."""
92
88
        self.sha1 = None
93
 
        self.size = None
94
89
        self.storage_kind = None
95
90
        self.key = None
96
91
        self.parents = None
104
99
    satisfies this, as does a list of lines.
105
100
 
106
101
    :ivar sha1: None, or the sha1 of the content fulltext.
107
 
    :ivar size: None, or the size of the content fulltext.
108
102
    :ivar storage_kind: The native storage kind of this factory. Always
109
103
        'chunked'
110
104
    :ivar key: The key of this content. Each key is a tuple with a single
112
106
    :ivar parents: A tuple of parent keys for self.key. If the object has
113
107
        no parent information, None (as opposed to () for an empty list of
114
108
        parents).
115
 
    :ivar chunks_are_lines: Whether chunks are lines.
116
109
     """
117
110
 
118
 
    def __init__(self, key, parents, sha1, chunks, chunks_are_lines=None):
 
111
    def __init__(self, key, parents, sha1, chunks):
119
112
        """Create a ContentFactory."""
120
113
        self.sha1 = sha1
121
 
        self.size = sum(map(len, chunks))
122
114
        self.storage_kind = 'chunked'
123
115
        self.key = key
124
116
        self.parents = parents
125
117
        self._chunks = chunks
126
 
        self._chunks_are_lines = chunks_are_lines
127
118
 
128
119
    def get_bytes_as(self, storage_kind):
129
120
        if storage_kind == 'chunked':
130
121
            return self._chunks
131
122
        elif storage_kind == 'fulltext':
132
 
            return b''.join(self._chunks)
133
 
        elif storage_kind == 'lines':
134
 
            if self._chunks_are_lines:
135
 
                return self._chunks
136
 
            return list(osutils.chunks_to_lines(self._chunks))
 
123
            return ''.join(self._chunks)
137
124
        raise errors.UnavailableRepresentation(self.key, storage_kind,
138
 
                                               self.storage_kind)
 
125
            self.storage_kind)
139
126
 
140
 
    def iter_bytes_as(self, storage_kind):
141
 
        if storage_kind == 'chunked':
142
 
            return iter(self._chunks)
143
 
        elif storage_kind == 'lines':
144
 
            if self._chunks_are_lines:
145
 
                return iter(self._chunks)
146
 
            return iter(osutils.chunks_to_lines(self._chunks))
147
 
        raise errors.UnavailableRepresentation(self.key, storage_kind,
148
 
                                               self.storage_kind)
149
127
 
150
128
class FulltextContentFactory(ContentFactory):
151
129
    """Static data content factory.
166
144
    def __init__(self, key, parents, sha1, text):
167
145
        """Create a ContentFactory."""
168
146
        self.sha1 = sha1
169
 
        self.size = len(text)
170
147
        self.storage_kind = 'fulltext'
171
148
        self.key = key
172
149
        self.parents = parents
173
 
        if not isinstance(text, bytes):
174
 
            raise TypeError(text)
175
150
        self._text = text
176
151
 
177
152
    def get_bytes_as(self, storage_kind):
179
154
            return self._text
180
155
        elif storage_kind == 'chunked':
181
156
            return [self._text]
182
 
        elif storage_kind == 'lines':
183
 
            return osutils.split_lines(self._text)
184
 
        raise errors.UnavailableRepresentation(self.key, storage_kind,
185
 
                                               self.storage_kind)
186
 
 
187
 
    def iter_bytes_as(self, storage_kind):
188
 
        if storage_kind == 'chunked':
189
 
            return iter([self._text])
190
 
        elif storage_kind == 'lines':
191
 
            return iter(osutils.split_lines(self._text))
192
 
        raise errors.UnavailableRepresentation(self.key, storage_kind,
193
 
                                               self.storage_kind)
194
 
 
195
 
 
196
 
class FileContentFactory(ContentFactory):
197
 
    """File-based content factory.
198
 
    """
199
 
 
200
 
    def __init__(self, key, parents, fileobj, sha1=None, size=None):
201
 
        self.key = key
202
 
        self.parents = parents
203
 
        self.file = fileobj
204
 
        self.storage_kind = 'file'
205
 
        self.sha1 = sha1
206
 
        self.size = size
207
 
 
208
 
    def get_bytes_as(self, storage_kind):
209
 
        self.file.seek(0)
210
 
        if storage_kind == 'fulltext':
211
 
            return self.file.read()
212
 
        elif storage_kind == 'chunked':
213
 
            return list(osutils.file_iterator(self.file))
214
 
        elif storage_kind == 'lines':
215
 
            return list(self.file.readlines())
216
 
        raise errors.UnavailableRepresentation(self.key, storage_kind,
217
 
                                               self.storage_kind)
218
 
 
219
 
    def iter_bytes_as(self, storage_kind):
220
 
        self.file.seek(0)
221
 
        if storage_kind == 'chunked':
222
 
            return osutils.file_iterator(self.file)
223
 
        elif storage_kind == 'lines':
224
 
            return self.file
225
 
        raise errors.UnavailableRepresentation(self.key, storage_kind,
226
 
                                               self.storage_kind)
 
157
        raise errors.UnavailableRepresentation(self.key, storage_kind,
 
158
            self.storage_kind)
227
159
 
228
160
 
229
161
class AbsentContentFactory(ContentFactory):
239
171
    def __init__(self, key):
240
172
        """Create a ContentFactory."""
241
173
        self.sha1 = None
242
 
        self.size = None
243
174
        self.storage_kind = 'absent'
244
175
        self.key = key
245
176
        self.parents = None
250
181
                         ' code does not handle if it is missing.'
251
182
                         % (self.key,))
252
183
 
253
 
    def iter_bytes_as(self, storage_kind):
254
 
        raise ValueError('A request was made for key: %s, but that'
255
 
                         ' content is not available, and the calling'
256
 
                         ' code does not handle if it is missing.'
257
 
                         % (self.key,))
258
 
 
259
184
 
260
185
class AdapterFactory(ContentFactory):
261
186
    """A content factory to adapt between key prefix's."""
281
206
            yield record
282
207
 
283
208
 
284
 
class _MPDiffGenerator(object):
285
 
    """Pull out the functionality for generating mp_diffs."""
286
 
 
287
 
    def __init__(self, vf, keys):
288
 
        self.vf = vf
289
 
        # This is the order the keys were requested in
290
 
        self.ordered_keys = tuple(keys)
291
 
        # keys + their parents, what we need to compute the diffs
292
 
        self.needed_keys = ()
293
 
        # Map from key: mp_diff
294
 
        self.diffs = {}
295
 
        # Map from key: parents_needed (may have ghosts)
296
 
        self.parent_map = {}
297
 
        # Parents that aren't present
298
 
        self.ghost_parents = ()
299
 
        # Map from parent_key => number of children for this text
300
 
        self.refcounts = {}
301
 
        # Content chunks that are cached while we still need them
302
 
        self.chunks = {}
303
 
 
304
 
    def _find_needed_keys(self):
305
 
        """Find the set of keys we need to request.
306
 
 
307
 
        This includes all the original keys passed in, and the non-ghost
308
 
        parents of those keys.
309
 
 
310
 
        :return: (needed_keys, refcounts)
311
 
            needed_keys is the set of all texts we need to extract
312
 
            refcounts is a dict of {key: num_children} letting us know when we
313
 
                no longer need to cache a given parent text
314
 
        """
315
 
        # All the keys and their parents
316
 
        needed_keys = set(self.ordered_keys)
317
 
        parent_map = self.vf.get_parent_map(needed_keys)
318
 
        self.parent_map = parent_map
319
 
        # TODO: Should we be using a different construct here? I think this
320
 
        #       uses difference_update internally, and we expect the result to
321
 
        #       be tiny
322
 
        missing_keys = needed_keys.difference(parent_map)
323
 
        if missing_keys:
324
 
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
325
 
        # Parents that might be missing. They are allowed to be ghosts, but we
326
 
        # should check for them
327
 
        refcounts = {}
328
 
        setdefault = refcounts.setdefault
329
 
        just_parents = set()
330
 
        for child_key, parent_keys in viewitems(parent_map):
331
 
            if not parent_keys:
332
 
                # parent_keys may be None if a given VersionedFile claims to
333
 
                # not support graph operations.
334
 
                continue
335
 
            just_parents.update(parent_keys)
336
 
            needed_keys.update(parent_keys)
337
 
            for p in parent_keys:
338
 
                refcounts[p] = setdefault(p, 0) + 1
339
 
        just_parents.difference_update(parent_map)
340
 
        # Remove any parents that are actually ghosts from the needed set
341
 
        self.present_parents = set(self.vf.get_parent_map(just_parents))
342
 
        self.ghost_parents = just_parents.difference(self.present_parents)
343
 
        needed_keys.difference_update(self.ghost_parents)
344
 
        self.needed_keys = needed_keys
345
 
        self.refcounts = refcounts
346
 
        return needed_keys, refcounts
347
 
 
348
 
    def _compute_diff(self, key, parent_lines, lines):
349
 
        """Compute a single mp_diff, and store it in self._diffs"""
350
 
        if len(parent_lines) > 0:
351
 
            # XXX: _extract_blocks is not usefully defined anywhere...
352
 
            #      It was meant to extract the left-parent diff without
353
 
            #      having to recompute it for Knit content (pack-0.92,
354
 
            #      etc). That seems to have regressed somewhere
355
 
            left_parent_blocks = self.vf._extract_blocks(key,
356
 
                                                         parent_lines[0], lines)
357
 
        else:
358
 
            left_parent_blocks = None
359
 
        diff = multiparent.MultiParent.from_lines(lines,
360
 
                                                  parent_lines, left_parent_blocks)
361
 
        self.diffs[key] = diff
362
 
 
363
 
    def _process_one_record(self, key, this_chunks):
364
 
        parent_keys = None
365
 
        if key in self.parent_map:
366
 
            # This record should be ready to diff, since we requested
367
 
            # content in 'topological' order
368
 
            parent_keys = self.parent_map.pop(key)
369
 
            # If a VersionedFile claims 'no-graph' support, then it may return
370
 
            # None for any parent request, so we replace it with an empty tuple
371
 
            if parent_keys is None:
372
 
                parent_keys = ()
373
 
            parent_lines = []
374
 
            for p in parent_keys:
375
 
                # Alternatively we could check p not in self.needed_keys, but
376
 
                # ghost_parents should be tiny versus huge
377
 
                if p in self.ghost_parents:
378
 
                    continue
379
 
                refcount = self.refcounts[p]
380
 
                if refcount == 1:  # Last child reference
381
 
                    self.refcounts.pop(p)
382
 
                    parent_chunks = self.chunks.pop(p)
383
 
                else:
384
 
                    self.refcounts[p] = refcount - 1
385
 
                    parent_chunks = self.chunks[p]
386
 
                p_lines = osutils.chunks_to_lines(parent_chunks)
387
 
                # TODO: Should we cache the line form? We did the
388
 
                #       computation to get it, but storing it this way will
389
 
                #       be less memory efficient...
390
 
                parent_lines.append(p_lines)
391
 
                del p_lines
392
 
            lines = osutils.chunks_to_lines(this_chunks)
393
 
            # Since we needed the lines, we'll go ahead and cache them this way
394
 
            this_chunks = lines
395
 
            self._compute_diff(key, parent_lines, lines)
396
 
            del lines
397
 
        # Is this content required for any more children?
398
 
        if key in self.refcounts:
399
 
            self.chunks[key] = this_chunks
400
 
 
401
 
    def _extract_diffs(self):
402
 
        needed_keys, refcounts = self._find_needed_keys()
403
 
        for record in self.vf.get_record_stream(needed_keys,
404
 
                                                'topological', True):
405
 
            if record.storage_kind == 'absent':
406
 
                raise errors.RevisionNotPresent(record.key, self.vf)
407
 
            self._process_one_record(record.key,
408
 
                                     record.get_bytes_as('chunked'))
409
 
 
410
 
    def compute_diffs(self):
411
 
        self._extract_diffs()
412
 
        dpop = self.diffs.pop
413
 
        return [dpop(k) for k in self.ordered_keys]
414
 
 
415
 
 
416
209
class VersionedFile(object):
417
210
    """Versioned text file storage.
418
211
 
466
259
        raise NotImplementedError
467
260
 
468
261
    def add_lines(self, version_id, parents, lines, parent_texts=None,
469
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
470
 
                  check_content=True):
 
262
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
263
        check_content=True):
471
264
        """Add a single text on top of the versioned file.
472
265
 
473
266
        Must raise RevisionAlreadyPresent if the new version is
507
300
        """
508
301
        self._check_write_ok()
509
302
        return self._add_lines(version_id, parents, lines, parent_texts,
510
 
                               left_matching_blocks, nostore_sha, random_id, check_content)
 
303
            left_matching_blocks, nostore_sha, random_id, check_content)
511
304
 
512
305
    def _add_lines(self, version_id, parents, lines, parent_texts,
513
 
                   left_matching_blocks, nostore_sha, random_id, check_content):
 
306
        left_matching_blocks, nostore_sha, random_id, check_content):
514
307
        """Helper to do the class specific add_lines."""
515
308
        raise NotImplementedError(self.add_lines)
516
309
 
517
310
    def add_lines_with_ghosts(self, version_id, parents, lines,
518
 
                              parent_texts=None, nostore_sha=None, random_id=False,
519
 
                              check_content=True, left_matching_blocks=None):
 
311
        parent_texts=None, nostore_sha=None, random_id=False,
 
312
        check_content=True, left_matching_blocks=None):
520
313
        """Add lines to the versioned file, allowing ghosts to be present.
521
314
 
522
315
        This takes the same parameters as add_lines and returns the same.
523
316
        """
524
317
        self._check_write_ok()
525
318
        return self._add_lines_with_ghosts(version_id, parents, lines,
526
 
                                           parent_texts, nostore_sha, random_id, check_content, left_matching_blocks)
 
319
            parent_texts, nostore_sha, random_id, check_content, left_matching_blocks)
527
320
 
528
321
    def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts,
529
 
                               nostore_sha, random_id, check_content, left_matching_blocks):
 
322
        nostore_sha, random_id, check_content, left_matching_blocks):
530
323
        """Helper to do class specific add_lines_with_ghosts."""
531
324
        raise NotImplementedError(self.add_lines_with_ghosts)
532
325
 
537
330
    def _check_lines_not_unicode(self, lines):
538
331
        """Check that lines being added to a versioned file are not unicode."""
539
332
        for line in lines:
540
 
            if not isinstance(line, bytes):
 
333
            if line.__class__ is not str:
541
334
                raise errors.BzrBadParameterUnicode("lines")
542
335
 
543
336
    def _check_lines_are_lines(self, lines):
544
337
        """Check that the lines really are full lines without inline EOL."""
545
338
        for line in lines:
546
 
            if b'\n' in line[:-1]:
 
339
            if '\n' in line[:-1]:
547
340
                raise errors.BzrBadParameterContainsNewline("lines")
548
341
 
549
342
    def get_format_signature(self):
555
348
 
556
349
    def make_mpdiffs(self, version_ids):
557
350
        """Create multiparent diffs for specified versions."""
558
 
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
559
 
        #      is a list of strings, not keys. And while self.get_record_stream
560
 
        #      is supported, it takes *keys*, while self.get_parent_map() takes
561
 
        #      strings... *sigh*
562
351
        knit_versions = set()
563
352
        knit_versions.update(version_ids)
564
353
        parent_map = self.get_parent_map(version_ids)
568
357
            except KeyError:
569
358
                raise errors.RevisionNotPresent(version_id, self)
570
359
        # We need to filter out ghosts, because we can't diff against them.
571
 
        knit_versions = set(self.get_parent_map(knit_versions))
 
360
        knit_versions = set(self.get_parent_map(knit_versions).keys())
572
361
        lines = dict(zip(knit_versions,
573
 
                         self._get_lf_split_line_list(knit_versions)))
 
362
            self._get_lf_split_line_list(knit_versions)))
574
363
        diffs = []
575
364
        for version_id in version_ids:
576
365
            target = lines[version_id]
577
366
            try:
578
367
                parents = [lines[p] for p in parent_map[version_id] if p in
579
 
                           knit_versions]
 
368
                    knit_versions]
580
369
            except KeyError:
581
370
                # I don't know how this could ever trigger.
582
371
                # parent_map[version_id] was already triggered in the previous
589
378
            else:
590
379
                left_parent_blocks = None
591
380
            diffs.append(multiparent.MultiParent.from_lines(target, parents,
592
 
                                                            left_parent_blocks))
 
381
                         left_parent_blocks))
593
382
        return diffs
594
383
 
595
384
    def _extract_blocks(self, version_id, source, target):
612
401
        for version, parent_ids, expected_sha1, mpdiff in records:
613
402
            needed_parents.update(p for p in parent_ids
614
403
                                  if not mpvf.has_version(p))
615
 
        present_parents = set(self.get_parent_map(needed_parents))
 
404
        present_parents = set(self.get_parent_map(needed_parents).keys())
616
405
        for parent_id, lines in zip(present_parents,
617
 
                                    self._get_lf_split_line_list(present_parents)):
 
406
                                 self._get_lf_split_line_list(present_parents)):
618
407
            mpvf.add_version(lines, parent_id, [])
619
 
        for (version, parent_ids, expected_sha1, mpdiff), lines in zip(
620
 
                records, mpvf.get_line_list(versions)):
 
408
        for (version, parent_ids, expected_sha1, mpdiff), lines in\
 
409
            zip(records, mpvf.get_line_list(versions)):
621
410
            if len(parent_ids) == 1:
622
411
                left_matching_blocks = list(mpdiff.get_matching_blocks(0,
623
 
                                                                       mpvf.get_diff(parent_ids[0]).num_lines()))
 
412
                    mpvf.get_diff(parent_ids[0]).num_lines()))
624
413
            else:
625
414
                left_matching_blocks = None
626
415
            try:
627
416
                _, _, version_text = self.add_lines_with_ghosts(version,
628
 
                                                                parent_ids, lines, vf_parents,
629
 
                                                                left_matching_blocks=left_matching_blocks)
 
417
                    parent_ids, lines, vf_parents,
 
418
                    left_matching_blocks=left_matching_blocks)
630
419
            except NotImplementedError:
631
420
                # The vf can't handle ghosts, so add lines normally, which will
632
421
                # (reasonably) fail if there are ghosts in the data.
633
422
                _, _, version_text = self.add_lines(version,
634
 
                                                    parent_ids, lines, vf_parents,
635
 
                                                    left_matching_blocks=left_matching_blocks)
 
423
                    parent_ids, lines, vf_parents,
 
424
                    left_matching_blocks=left_matching_blocks)
636
425
            vf_parents[version] = version_text
637
426
        sha1s = self.get_sha1s(versions)
638
427
        for version, parent_ids, expected_sha1, mpdiff in records:
645
434
        Raises RevisionNotPresent if version is not present in
646
435
        file history.
647
436
        """
648
 
        return b''.join(self.get_lines(version_id))
 
437
        return ''.join(self.get_lines(version_id))
649
438
    get_string = get_text
650
439
 
651
440
    def get_texts(self, version_ids):
654
443
        Raises RevisionNotPresent if version is not present in
655
444
        file history.
656
445
        """
657
 
        return [b''.join(self.get_lines(v)) for v in version_ids]
 
446
        return [''.join(self.get_lines(v)) for v in version_ids]
658
447
 
659
448
    def get_lines(self, version_id):
660
449
        """Return version contents as a sequence of lines.
665
454
        raise NotImplementedError(self.get_lines)
666
455
 
667
456
    def _get_lf_split_line_list(self, version_ids):
668
 
        return [BytesIO(t).readlines() for t in self.get_texts(version_ids)]
 
457
        return [StringIO(t).readlines() for t in self.get_texts(version_ids)]
669
458
 
670
459
    def get_ancestry(self, version_ids, topo_sorted=True):
671
460
        """Return a list of all ancestors of given version(s). This
676
465
 
677
466
        Must raise RevisionNotPresent if any of the given versions are
678
467
        not present in file history."""
 
468
        if isinstance(version_ids, basestring):
 
469
            version_ids = [version_ids]
679
470
        raise NotImplementedError(self.get_ancestry)
680
471
 
681
472
    def get_ancestry_with_ghosts(self, version_ids):
742
533
        """
743
534
        raise NotImplementedError(self.iter_lines_added_or_present_in_versions)
744
535
 
745
 
    def plan_merge(self, ver_a, ver_b, base=None):
 
536
    def plan_merge(self, ver_a, ver_b):
746
537
        """Return pseudo-annotation indicating how the two versions merge.
747
538
 
748
539
        This is computed between versions a and b and their common
787
578
        self.calls = []
788
579
 
789
580
    def add_lines(self, key, parents, lines, parent_texts=None,
790
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
791
 
                  check_content=True):
 
581
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
582
        check_content=True):
792
583
        self.calls.append(("add_lines", key, parents, lines, parent_texts,
793
 
                           left_matching_blocks, nostore_sha, random_id, check_content))
 
584
            left_matching_blocks, nostore_sha, random_id, check_content))
794
585
        return self._backing_vf.add_lines(key, parents, lines, parent_texts,
795
 
                                          left_matching_blocks, nostore_sha, random_id, check_content)
796
 
 
797
 
    def add_content(self, factory, parent_texts=None,
798
 
                    left_matching_blocks=None, nostore_sha=None, random_id=False,
799
 
                    check_content=True):
800
 
        self.calls.append(("add_content", factory, parent_texts,
801
 
                           left_matching_blocks, nostore_sha, random_id, check_content))
802
 
        return self._backing_vf.add_content(
803
 
            factory, parent_texts, left_matching_blocks, nostore_sha,
804
 
            random_id, check_content)
 
586
            left_matching_blocks, nostore_sha, random_id, check_content)
805
587
 
806
588
    def check(self):
807
589
        self._backing_vf.check()
812
594
 
813
595
    def get_record_stream(self, keys, sort_order, include_delta_closure):
814
596
        self.calls.append(("get_record_stream", list(keys), sort_order,
815
 
                           include_delta_closure))
 
597
            include_delta_closure))
816
598
        return self._backing_vf.get_record_stream(keys, sort_order,
817
 
                                                  include_delta_closure)
 
599
            include_delta_closure)
818
600
 
819
601
    def get_sha1s(self, keys):
820
602
        self.calls.append(("get_sha1s", copy(keys)))
851
633
 
852
634
    def get_record_stream(self, keys, sort_order, include_delta_closure):
853
635
        self.calls.append(("get_record_stream", list(keys), sort_order,
854
 
                           include_delta_closure))
 
636
            include_delta_closure))
855
637
        if sort_order == 'unordered':
856
638
            def sort_key(key):
857
639
                return (self._key_priority.get(key, 0), key)
859
641
            # backing_vf
860
642
            for key in sorted(keys, key=sort_key):
861
643
                for record in self._backing_vf.get_record_stream([key],
862
 
                                                                 'unordered', include_delta_closure):
 
644
                                'unordered', include_delta_closure):
863
645
                    yield record
864
646
        else:
865
647
            for record in self._backing_vf.get_record_stream(keys, sort_order,
866
 
                                                             include_delta_closure):
 
648
                            include_delta_closure):
867
649
                yield record
868
650
 
869
651
 
873
655
    def map(self, key):
874
656
        """Map key to an underlying storage identifier.
875
657
 
876
 
        :param key: A key tuple e.g. (b'file-id', b'revision-id').
 
658
        :param key: A key tuple e.g. ('file-id', 'revision-id').
877
659
        :return: An underlying storage identifier, specific to the partitioning
878
660
            mechanism.
879
661
        """
910
692
 
911
693
    def map(self, key):
912
694
        """See KeyMapper.map()."""
913
 
        return urlutils.quote(self._map(key))
 
695
        return urllib.quote(self._map(key))
914
696
 
915
697
    def unmap(self, partition_id):
916
698
        """See KeyMapper.unmap()."""
917
 
        return self._unmap(urlutils.unquote(partition_id))
 
699
        return self._unmap(urllib.unquote(partition_id))
918
700
 
919
701
 
920
702
class PrefixMapper(URLEscapeMapper):
925
707
 
926
708
    def _map(self, key):
927
709
        """See KeyMapper.map()."""
928
 
        return key[0].decode('utf-8')
 
710
        return key[0]
929
711
 
930
712
    def _unmap(self, partition_id):
931
713
        """See KeyMapper.unmap()."""
932
 
        return (partition_id.encode('utf-8'),)
 
714
        return (partition_id,)
933
715
 
934
716
 
935
717
class HashPrefixMapper(URLEscapeMapper):
941
723
    def _map(self, key):
942
724
        """See KeyMapper.map()."""
943
725
        prefix = self._escape(key[0])
944
 
        return "%02x/%s" % (adler32(prefix) & 0xff, prefix.decode('utf-8'))
 
726
        return "%02x/%s" % (adler32(prefix) & 0xff, prefix)
945
727
 
946
728
    def _escape(self, prefix):
947
729
        """No escaping needed here."""
949
731
 
950
732
    def _unmap(self, partition_id):
951
733
        """See KeyMapper.unmap()."""
952
 
        return (self._unescape(osutils.basename(partition_id)).encode('utf-8'),)
 
734
        return (self._unescape(osutils.basename(partition_id)),)
953
735
 
954
736
    def _unescape(self, basename):
955
737
        """No unescaping needed for HashPrefixMapper."""
962
744
    This mapper is for use with a transport based backend.
963
745
    """
964
746
 
965
 
    _safe = bytearray(b"abcdefghijklmnopqrstuvwxyz0123456789-_@,.")
 
747
    _safe = "abcdefghijklmnopqrstuvwxyz0123456789-_@,."
966
748
 
967
749
    def _escape(self, prefix):
968
750
        """Turn a key element into a filesystem safe string.
969
751
 
970
 
        This is similar to a plain urlutils.quote, except
 
752
        This is similar to a plain urllib.quote, except
971
753
        it uses specific safe characters, so that it doesn't
972
754
        have to translate a lot of valid file ids.
973
755
        """
974
756
        # @ does not get escaped. This is because it is a valid
975
757
        # filesystem character we use all the time, and it looks
976
758
        # a lot better than seeing %40 all the time.
977
 
        r = [(c in self._safe) and chr(c) or ('%%%02x' % c)
978
 
             for c in bytearray(prefix)]
979
 
        return ''.join(r).encode('ascii')
 
759
        r = [((c in self._safe) and c or ('%%%02x' % ord(c)))
 
760
             for c in prefix]
 
761
        return ''.join(r)
980
762
 
981
763
    def _unescape(self, basename):
982
764
        """Escaped names are easily unescaped by urlutils."""
983
 
        return urlutils.unquote(basename)
 
765
        return urllib.unquote(basename)
984
766
 
985
767
 
986
768
def make_versioned_files_factory(versioned_file_factory, mapper):
992
774
    """
993
775
    def factory(transport):
994
776
        return ThunkedVersionedFiles(transport, versioned_file_factory, mapper,
995
 
                                     lambda: True)
 
777
            lambda:True)
996
778
    return factory
997
779
 
998
780
 
1007
789
 
1008
790
    The keyspace is expressed via simple tuples. Any instance of VersionedFiles
1009
791
    may have a different length key-size, but that size will be constant for
1010
 
    all texts added to or retrieved from it. For instance, breezy uses
 
792
    all texts added to or retrieved from it. For instance, bzrlib uses
1011
793
    instances with a key-size of 2 for storing user files in a repository, with
1012
794
    the first element the fileid, and the second the version of that file.
1013
795
 
1014
796
    The use of tuples allows a single code base to support several different
1015
797
    uses with only the mapping logic changing from instance to instance.
1016
 
 
1017
 
    :ivar _immediate_fallback_vfs: For subclasses that support stacking,
1018
 
        this is a list of other VersionedFiles immediately underneath this
1019
 
        one.  They may in turn each have further fallbacks.
1020
798
    """
1021
799
 
1022
800
    def add_lines(self, key, parents, lines, parent_texts=None,
1023
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
1024
 
                  check_content=True):
 
801
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
802
        check_content=True):
1025
803
        """Add a text to the store.
1026
804
 
1027
805
        :param key: The key tuple of the text to add. If the last element is
1058
836
        """
1059
837
        raise NotImplementedError(self.add_lines)
1060
838
 
1061
 
    def add_content(self, factory, parent_texts=None,
1062
 
                    left_matching_blocks=None, nostore_sha=None, random_id=False,
1063
 
                    check_content=True):
1064
 
        """Add a text to the store from a chunk iterable.
 
839
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
 
840
        """Add a text to the store.
 
841
 
 
842
        This is a private function for use by CommitBuilder.
1065
843
 
1066
844
        :param key: The key tuple of the text to add. If the last element is
1067
845
            None, a CHK string will be generated during the addition.
1068
846
        :param parents: The parents key tuples of the text to add.
1069
 
        :param chunk_iter: An iterable over bytestrings.
1070
 
        :param parent_texts: An optional dictionary containing the opaque
1071
 
            representations of some or all of the parents of version_id to
1072
 
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
1073
 
            returned by add_lines or data corruption can be caused.
1074
 
        :param left_matching_blocks: a hint about which areas are common
1075
 
            between the text and its left-hand-parent.  The format is
1076
 
            the SequenceMatcher.get_matching_blocks format.
 
847
        :param text: A string containing the text to be committed.
1077
848
        :param nostore_sha: Raise ExistingContent and do not add the lines to
1078
849
            the versioned file if the digest of the lines matches this.
1079
850
        :param random_id: If True a random id has been selected rather than
1086
857
            bytestrings that are correctly formed lines.
1087
858
        :return: The text sha1, the number of bytes in the text, and an opaque
1088
859
                 representation of the inserted version which can be provided
1089
 
                 back to future add_lines calls in the parent_texts dictionary.
 
860
                 back to future _add_text calls in the parent_texts dictionary.
1090
861
        """
1091
 
        raise NotImplementedError(self.add_content)
 
862
        # The default implementation just thunks over to .add_lines(),
 
863
        # inefficient, but it works.
 
864
        return self.add_lines(key, parents, osutils.split_lines(text),
 
865
                              nostore_sha=nostore_sha,
 
866
                              random_id=random_id,
 
867
                              check_content=True)
1092
868
 
1093
869
    def add_mpdiffs(self, records):
1094
870
        """Add mpdiffs to this VersionedFile.
1108
884
                                  if not mpvf.has_version(p))
1109
885
        # It seems likely that adding all the present parents as fulltexts can
1110
886
        # easily exhaust memory.
 
887
        chunks_to_lines = osutils.chunks_to_lines
1111
888
        for record in self.get_record_stream(needed_parents, 'unordered',
1112
 
                                             True):
 
889
            True):
1113
890
            if record.storage_kind == 'absent':
1114
891
                continue
1115
 
            mpvf.add_version(record.get_bytes_as('lines'), record.key, [])
1116
 
        for (key, parent_keys, expected_sha1, mpdiff), lines in zip(
1117
 
                records, mpvf.get_line_list(versions)):
 
892
            mpvf.add_version(chunks_to_lines(record.get_bytes_as('chunked')),
 
893
                record.key, [])
 
894
        for (key, parent_keys, expected_sha1, mpdiff), lines in\
 
895
            zip(records, mpvf.get_line_list(versions)):
1118
896
            if len(parent_keys) == 1:
1119
897
                left_matching_blocks = list(mpdiff.get_matching_blocks(0,
1120
 
                                                                       mpvf.get_diff(parent_keys[0]).num_lines()))
 
898
                    mpvf.get_diff(parent_keys[0]).num_lines()))
1121
899
            else:
1122
900
                left_matching_blocks = None
1123
901
            version_sha1, _, version_text = self.add_lines(key,
1124
 
                                                           parent_keys, lines, vf_parents,
1125
 
                                                           left_matching_blocks=left_matching_blocks)
 
902
                parent_keys, lines, vf_parents,
 
903
                left_matching_blocks=left_matching_blocks)
1126
904
            if version_sha1 != expected_sha1:
1127
905
                raise errors.VersionedFileInvalidChecksum(version)
1128
906
            vf_parents[key] = version_text
1136
914
 
1137
915
    def check(self, progress_bar=None):
1138
916
        """Check this object for integrity.
1139
 
 
 
917
        
1140
918
        :param progress_bar: A progress bar to output as the check progresses.
1141
919
        :param keys: Specific keys within the VersionedFiles to check. When
1142
920
            this parameter is not None, check() becomes a generator as per
1161
939
    def _check_lines_not_unicode(self, lines):
1162
940
        """Check that lines being added to a versioned file are not unicode."""
1163
941
        for line in lines:
1164
 
            if line.__class__ is not bytes:
 
942
            if line.__class__ is not str:
1165
943
                raise errors.BzrBadParameterUnicode("lines")
1166
944
 
1167
945
    def _check_lines_are_lines(self, lines):
1168
946
        """Check that the lines really are full lines without inline EOL."""
1169
947
        for line in lines:
1170
 
            if b'\n' in line[:-1]:
 
948
            if '\n' in line[:-1]:
1171
949
                raise errors.BzrBadParameterContainsNewline("lines")
1172
950
 
1173
951
    def get_known_graph_ancestry(self, keys):
1178
956
        while pending:
1179
957
            this_parent_map = self.get_parent_map(pending)
1180
958
            parent_map.update(this_parent_map)
1181
 
            pending = set(itertools.chain.from_iterable(
1182
 
                viewvalues(this_parent_map)))
1183
 
            pending.difference_update(parent_map)
 
959
            pending = set()
 
960
            map(pending.update, this_parent_map.itervalues())
 
961
            pending = pending.difference(parent_map)
1184
962
        kg = _mod_graph.KnownGraph(parent_map)
1185
963
        return kg
1186
964
 
1217
995
        """
1218
996
        raise NotImplementedError(self.get_sha1s)
1219
997
 
1220
 
    __contains__ = index._has_key_from_parent_map
 
998
    has_key = index._has_key_from_parent_map
1221
999
 
1222
1000
    def get_missing_compression_parent_keys(self):
1223
1001
        """Return an iterable of keys of missing compression parents.
1269
1047
 
1270
1048
    def make_mpdiffs(self, keys):
1271
1049
        """Create multiparent diffs for specified keys."""
1272
 
        generator = _MPDiffGenerator(self, keys)
1273
 
        return generator.compute_diffs()
1274
 
 
1275
 
    def get_annotator(self):
1276
 
        return annotate.Annotator(self)
 
1050
        keys_order = tuple(keys)
 
1051
        keys = frozenset(keys)
 
1052
        knit_keys = set(keys)
 
1053
        parent_map = self.get_parent_map(keys)
 
1054
        for parent_keys in parent_map.itervalues():
 
1055
            if parent_keys:
 
1056
                knit_keys.update(parent_keys)
 
1057
        missing_keys = keys - set(parent_map)
 
1058
        if missing_keys:
 
1059
            raise errors.RevisionNotPresent(list(missing_keys)[0], self)
 
1060
        # We need to filter out ghosts, because we can't diff against them.
 
1061
        maybe_ghosts = knit_keys - keys
 
1062
        ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
 
1063
        knit_keys.difference_update(ghosts)
 
1064
        lines = {}
 
1065
        chunks_to_lines = osutils.chunks_to_lines
 
1066
        for record in self.get_record_stream(knit_keys, 'topological', True):
 
1067
            lines[record.key] = chunks_to_lines(record.get_bytes_as('chunked'))
 
1068
            # line_block_dict = {}
 
1069
            # for parent, blocks in record.extract_line_blocks():
 
1070
            #   line_blocks[parent] = blocks
 
1071
            # line_blocks[record.key] = line_block_dict
 
1072
        diffs = []
 
1073
        for key in keys_order:
 
1074
            target = lines[key]
 
1075
            parents = parent_map[key] or []
 
1076
            # Note that filtering knit_keys can lead to a parent difference
 
1077
            # between the creation and the application of the mpdiff.
 
1078
            parent_lines = [lines[p] for p in parents if p in knit_keys]
 
1079
            if len(parent_lines) > 0:
 
1080
                left_parent_blocks = self._extract_blocks(key, parent_lines[0],
 
1081
                    target)
 
1082
            else:
 
1083
                left_parent_blocks = None
 
1084
            diffs.append(multiparent.MultiParent.from_lines(target,
 
1085
                parent_lines, left_parent_blocks))
 
1086
        return diffs
1277
1087
 
1278
1088
    missing_keys = index._missing_keys_from_parent_map
1279
1089
 
1280
1090
    def _extract_blocks(self, version_id, source, target):
1281
1091
        return None
1282
1092
 
1283
 
    def _transitive_fallbacks(self):
1284
 
        """Return the whole stack of fallback versionedfiles.
1285
 
 
1286
 
        This VersionedFiles may have a list of fallbacks, but it doesn't
1287
 
        necessarily know about the whole stack going down, and it can't know
1288
 
        at open time because they may change after the objects are opened.
1289
 
        """
1290
 
        all_fallbacks = []
1291
 
        for a_vfs in self._immediate_fallback_vfs:
1292
 
            all_fallbacks.append(a_vfs)
1293
 
            all_fallbacks.extend(a_vfs._transitive_fallbacks())
1294
 
        return all_fallbacks
1295
 
 
1296
1093
 
1297
1094
class ThunkedVersionedFiles(VersionedFiles):
1298
1095
    """Storage for many versioned files thunked onto a 'VersionedFile' class.
1311
1108
        self._mapper = mapper
1312
1109
        self._is_locked = is_locked
1313
1110
 
1314
 
    def add_content(self, factory, parent_texts=None,
1315
 
                    left_matching_blocks=None, nostore_sha=None, random_id=False):
1316
 
        """See VersionedFiles.add_content()."""
1317
 
        lines = factory.get_bytes_as('lines')
1318
 
        return self.add_lines(
1319
 
            factory.key, factory.parents, lines,
1320
 
            parent_texts=parent_texts,
1321
 
            left_matching_blocks=left_matching_blocks,
1322
 
            nostore_sha=nostore_sha,
1323
 
            random_id=random_id,
1324
 
            check_content=True)
1325
 
 
1326
1111
    def add_lines(self, key, parents, lines, parent_texts=None,
1327
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
1328
 
                  check_content=True):
 
1112
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
1113
        check_content=True):
1329
1114
        """See VersionedFiles.add_lines()."""
1330
1115
        path = self._mapper.map(key)
1331
1116
        version_id = key[-1]
1334
1119
        try:
1335
1120
            try:
1336
1121
                return vf.add_lines_with_ghosts(version_id, parents, lines,
1337
 
                                                parent_texts=parent_texts,
1338
 
                                                left_matching_blocks=left_matching_blocks,
1339
 
                                                nostore_sha=nostore_sha, random_id=random_id,
1340
 
                                                check_content=check_content)
 
1122
                    parent_texts=parent_texts,
 
1123
                    left_matching_blocks=left_matching_blocks,
 
1124
                    nostore_sha=nostore_sha, random_id=random_id,
 
1125
                    check_content=check_content)
1341
1126
            except NotImplementedError:
1342
1127
                return vf.add_lines(version_id, parents, lines,
1343
 
                                    parent_texts=parent_texts,
1344
 
                                    left_matching_blocks=left_matching_blocks,
1345
 
                                    nostore_sha=nostore_sha, random_id=random_id,
1346
 
                                    check_content=check_content)
 
1128
                    parent_texts=parent_texts,
 
1129
                    left_matching_blocks=left_matching_blocks,
 
1130
                    nostore_sha=nostore_sha, random_id=random_id,
 
1131
                    check_content=check_content)
1347
1132
        except errors.NoSuchFile:
1348
1133
            # parent directory may be missing, try again.
1349
1134
            self._transport.mkdir(osutils.dirname(path))
1350
1135
            try:
1351
1136
                return vf.add_lines_with_ghosts(version_id, parents, lines,
1352
 
                                                parent_texts=parent_texts,
1353
 
                                                left_matching_blocks=left_matching_blocks,
1354
 
                                                nostore_sha=nostore_sha, random_id=random_id,
1355
 
                                                check_content=check_content)
 
1137
                    parent_texts=parent_texts,
 
1138
                    left_matching_blocks=left_matching_blocks,
 
1139
                    nostore_sha=nostore_sha, random_id=random_id,
 
1140
                    check_content=check_content)
1356
1141
            except NotImplementedError:
1357
1142
                return vf.add_lines(version_id, parents, lines,
1358
 
                                    parent_texts=parent_texts,
1359
 
                                    left_matching_blocks=left_matching_blocks,
1360
 
                                    nostore_sha=nostore_sha, random_id=random_id,
1361
 
                                    check_content=check_content)
 
1143
                    parent_texts=parent_texts,
 
1144
                    left_matching_blocks=left_matching_blocks,
 
1145
                    nostore_sha=nostore_sha, random_id=random_id,
 
1146
                    check_content=check_content)
1362
1147
 
1363
1148
    def annotate(self, key):
1364
1149
        """Return a list of (version-key, line) tuples for the text of key.
1374
1159
            result.append((prefix + (origin,), line))
1375
1160
        return result
1376
1161
 
 
1162
    def get_annotator(self):
 
1163
        return annotate.Annotator(self)
 
1164
 
1377
1165
    def check(self, progress_bar=None, keys=None):
1378
1166
        """See VersionedFiles.check()."""
1379
1167
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1380
 
        # this is tolerable. Ideally we'd pass keys down to check() and
 
1168
        # this is tolerable. Ideally we'd pass keys down to check() and 
1381
1169
        # have the older VersiondFile interface updated too.
1382
1170
        for prefix, vf in self._iter_all_components():
1383
1171
            vf.check()
1393
1181
        """
1394
1182
        prefixes = self._partition_keys(keys)
1395
1183
        result = {}
1396
 
        for prefix, suffixes in viewitems(prefixes):
 
1184
        for prefix, suffixes in prefixes.items():
1397
1185
            path = self._mapper.map(prefix)
1398
1186
            vf = self._get_vf(path)
1399
1187
            parent_map = vf.get_parent_map(suffixes)
1400
 
            for key, parents in viewitems(parent_map):
 
1188
            for key, parents in parent_map.items():
1401
1189
                result[prefix + (key,)] = tuple(
1402
1190
                    prefix + (parent,) for parent in parents)
1403
1191
        return result
1406
1194
        if not self._is_locked():
1407
1195
            raise errors.ObjectNotLocked(self)
1408
1196
        return self._file_factory(path, self._transport, create=True,
1409
 
                                  get_scope=lambda: None)
 
1197
            get_scope=lambda:None)
1410
1198
 
1411
1199
    def _partition_keys(self, keys):
1412
1200
        """Turn keys into a dict of prefix:suffix_list."""
1416
1204
            prefix_keys.append(key[-1])
1417
1205
        return result
1418
1206
 
1419
 
    def _iter_all_prefixes(self):
 
1207
    def _get_all_prefixes(self):
1420
1208
        # Identify all key prefixes.
1421
1209
        # XXX: A bit hacky, needs polish.
1422
 
        if isinstance(self._mapper, ConstantMapper):
 
1210
        if type(self._mapper) == ConstantMapper:
1423
1211
            paths = [self._mapper.map(())]
1424
1212
            prefixes = [()]
1425
1213
        else:
1439
1227
        for prefix, suffixes, vf in self._iter_keys_vf(keys):
1440
1228
            suffixes = [(suffix,) for suffix in suffixes]
1441
1229
            for record in vf.get_record_stream(suffixes, ordering,
1442
 
                                               include_delta_closure):
 
1230
                include_delta_closure):
1443
1231
                if record.parents is not None:
1444
1232
                    record.parents = tuple(
1445
1233
                        prefix + parent for parent in record.parents)
1449
1237
    def _iter_keys_vf(self, keys):
1450
1238
        prefixes = self._partition_keys(keys)
1451
1239
        sha1s = {}
1452
 
        for prefix, suffixes in viewitems(prefixes):
 
1240
        for prefix, suffixes in prefixes.items():
1453
1241
            path = self._mapper.map(prefix)
1454
1242
            vf = self._get_vf(path)
1455
1243
            yield prefix, suffixes, vf
1457
1245
    def get_sha1s(self, keys):
1458
1246
        """See VersionedFiles.get_sha1s()."""
1459
1247
        sha1s = {}
1460
 
        for prefix, suffixes, vf in self._iter_keys_vf(keys):
 
1248
        for prefix,suffixes, vf in self._iter_keys_vf(keys):
1461
1249
            vf_sha1s = vf.get_sha1s(suffixes)
1462
 
            for suffix, sha1 in viewitems(vf_sha1s):
 
1250
            for suffix, sha1 in vf_sha1s.iteritems():
1463
1251
                sha1s[prefix + (suffix,)] = sha1
1464
1252
        return sha1s
1465
1253
 
1511
1299
                yield line, prefix + (version,)
1512
1300
 
1513
1301
    def _iter_all_components(self):
1514
 
        for path, prefix in self._iter_all_prefixes():
 
1302
        for path, prefix in self._get_all_prefixes():
1515
1303
            yield prefix, self._get_vf(path)
1516
1304
 
1517
1305
    def keys(self):
1523
1311
        return result
1524
1312
 
1525
1313
 
1526
 
class VersionedFilesWithFallbacks(VersionedFiles):
1527
 
 
1528
 
    def without_fallbacks(self):
1529
 
        """Return a clone of this object without any fallbacks configured."""
1530
 
        raise NotImplementedError(self.without_fallbacks)
1531
 
 
1532
 
    def add_fallback_versioned_files(self, a_versioned_files):
1533
 
        """Add a source of texts for texts not present in this knit.
1534
 
 
1535
 
        :param a_versioned_files: A VersionedFiles object.
1536
 
        """
1537
 
        raise NotImplementedError(self.add_fallback_versioned_files)
1538
 
 
1539
 
    def get_known_graph_ancestry(self, keys):
1540
 
        """Get a KnownGraph instance with the ancestry of keys."""
1541
 
        parent_map, missing_keys = self._index.find_ancestry(keys)
1542
 
        for fallback in self._transitive_fallbacks():
1543
 
            if not missing_keys:
1544
 
                break
1545
 
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
1546
 
                missing_keys)
1547
 
            parent_map.update(f_parent_map)
1548
 
            missing_keys = f_missing_keys
1549
 
        kg = _mod_graph.KnownGraph(parent_map)
1550
 
        return kg
1551
 
 
1552
 
 
1553
1314
class _PlanMergeVersionedFile(VersionedFiles):
1554
1315
    """A VersionedFile for uncommitted and committed texts.
1555
1316
 
1576
1337
        # line data for locally held keys.
1577
1338
        self._lines = {}
1578
1339
        # key lookup providers
1579
 
        self._providers = [_mod_graph.DictParentsProvider(self._parents)]
 
1340
        self._providers = [DictParentsProvider(self._parents)]
1580
1341
 
1581
1342
    def plan_merge(self, ver_a, ver_b, base=None):
1582
1343
        """See VersionedFile.plan_merge"""
1583
 
        from ..merge import _PlanMerge
 
1344
        from bzrlib.merge import _PlanMerge
1584
1345
        if base is None:
1585
1346
            return _PlanMerge(ver_a, ver_b, self, (self._file_id,)).plan_merge()
1586
 
        old_plan = list(_PlanMerge(ver_a, base, self,
1587
 
                                   (self._file_id,)).plan_merge())
1588
 
        new_plan = list(_PlanMerge(ver_a, ver_b, self,
1589
 
                                   (self._file_id,)).plan_merge())
 
1347
        old_plan = list(_PlanMerge(ver_a, base, self, (self._file_id,)).plan_merge())
 
1348
        new_plan = list(_PlanMerge(ver_a, ver_b, self, (self._file_id,)).plan_merge())
1590
1349
        return _PlanMerge._subtract_plans(old_plan, new_plan)
1591
1350
 
1592
1351
    def plan_lca_merge(self, ver_a, ver_b, base=None):
1593
 
        from ..merge import _PlanLCAMerge
1594
 
        graph = _mod_graph.Graph(self)
1595
 
        new_plan = _PlanLCAMerge(
1596
 
            ver_a, ver_b, self, (self._file_id,), graph).plan_merge()
 
1352
        from bzrlib.merge import _PlanLCAMerge
 
1353
        graph = Graph(self)
 
1354
        new_plan = _PlanLCAMerge(ver_a, ver_b, self, (self._file_id,), graph).plan_merge()
1597
1355
        if base is None:
1598
1356
            return new_plan
1599
 
        old_plan = _PlanLCAMerge(
1600
 
            ver_a, base, self, (self._file_id,), graph).plan_merge()
 
1357
        old_plan = _PlanLCAMerge(ver_a, base, self, (self._file_id,), graph).plan_merge()
1601
1358
        return _PlanLCAMerge._subtract_plans(list(old_plan), list(new_plan))
1602
1359
 
1603
 
    def add_content(self, factory):
1604
 
        return self.add_lines(
1605
 
            factory.key, factory.parents, factory.get_bytes_as('lines'))
1606
 
 
1607
1360
    def add_lines(self, key, parents, lines):
1608
1361
        """See VersionedFiles.add_lines
1609
1362
 
1610
1363
        Lines are added locally, not to fallback versionedfiles.  Also, ghosts
1611
1364
        are permitted.  Only reserved ids are permitted.
1612
1365
        """
1613
 
        if not isinstance(key, tuple):
 
1366
        if type(key) is not tuple:
1614
1367
            raise TypeError(key)
1615
1368
        if not revision.is_reserved_id(key[-1]):
1616
1369
            raise ValueError('Only reserved ids may be used')
1628
1381
                lines = self._lines[key]
1629
1382
                parents = self._parents[key]
1630
1383
                pending.remove(key)
1631
 
                yield ChunkedContentFactory(
1632
 
                    key, parents, None, lines,
1633
 
                    chunks_are_lines=True)
 
1384
                yield ChunkedContentFactory(key, parents, None, lines)
1634
1385
        for versionedfile in self.fallback_versionedfiles:
1635
1386
            for record in versionedfile.get_record_stream(
1636
 
                    pending, 'unordered', True):
 
1387
                pending, 'unordered', True):
1637
1388
                if record.storage_kind == 'absent':
1638
1389
                    continue
1639
1390
                else:
1657
1408
            result[revision.NULL_REVISION] = ()
1658
1409
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1659
1410
        result.update(
1660
 
            _mod_graph.StackedParentsProvider(
1661
 
                self._providers).get_parent_map(keys))
1662
 
        for key, parents in viewitems(result):
 
1411
            StackedParentsProvider(self._providers).get_parent_map(keys))
 
1412
        for key, parents in result.iteritems():
1663
1413
            if parents == ():
1664
1414
                result[key] = (revision.NULL_REVISION,)
1665
1415
        return result
1734
1484
                ch_b = ch_a = True
1735
1485
            else:
1736
1486
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
1737
 
                                 'killed-base'):
 
1487
                        'killed-base'):
1738
1488
                    raise AssertionError(state)
1739
1489
        for struct in outstanding_struct():
1740
1490
            yield struct
1798
1548
    """Weave merge that takes a VersionedFile and two versions as its input."""
1799
1549
 
1800
1550
    def __init__(self, versionedfile, ver_a, ver_b,
1801
 
                 a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
 
1551
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1802
1552
        plan = versionedfile.plan_merge(ver_a, ver_b)
1803
1553
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1804
1554
 
1838
1588
 
1839
1589
    def get_parent_map(self, keys):
1840
1590
        """See VersionedFiles.get_parent_map."""
1841
 
        parent_view = viewitems(self._get_parent_map(k for (k,) in keys))
1842
 
        return dict(((k,), tuple((p,) for p in v)) for k, v in parent_view)
 
1591
        return dict([((k,), tuple([(p,) for p in v]))
 
1592
            for k,v in self._get_parent_map([k for (k,) in keys]).iteritems()])
1843
1593
 
1844
1594
    def get_sha1s(self, keys):
1845
1595
        """See VersionedFiles.get_sha1s."""
1859
1609
            if lines is not None:
1860
1610
                if not isinstance(lines, list):
1861
1611
                    raise AssertionError
1862
 
                yield ChunkedContentFactory(
1863
 
                    (k,), None, sha1=osutils.sha_strings(lines),
1864
 
                    chunks=lines, chunks_are_lines=True)
 
1612
                yield ChunkedContentFactory((k,), None,
 
1613
                        sha1=osutils.sha_strings(lines),
 
1614
                        chunks=lines)
1865
1615
            else:
1866
1616
                yield AbsentContentFactory((k,))
1867
1617
 
1874
1624
                yield (l, key)
1875
1625
 
1876
1626
 
1877
 
class NoDupeAddLinesDecorator(object):
1878
 
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
1879
 
    is already present.
1880
 
    """
1881
 
 
1882
 
    def __init__(self, store):
1883
 
        self._store = store
1884
 
 
1885
 
    def add_lines(self, key, parents, lines, parent_texts=None,
1886
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
1887
 
                  check_content=True):
1888
 
        """See VersionedFiles.add_lines.
1889
 
 
1890
 
        This implementation may return None as the third element of the return
1891
 
        value when the original store wouldn't.
1892
 
        """
1893
 
        if nostore_sha:
1894
 
            raise NotImplementedError(
1895
 
                "NoDupeAddLinesDecorator.add_lines does not implement the "
1896
 
                "nostore_sha behaviour.")
1897
 
        if key[-1] is None:
1898
 
            sha1 = osutils.sha_strings(lines)
1899
 
            key = (b"sha1:" + sha1,)
1900
 
        else:
1901
 
            sha1 = None
1902
 
        if key in self._store.get_parent_map([key]):
1903
 
            # This key has already been inserted, so don't do it again.
1904
 
            if sha1 is None:
1905
 
                sha1 = osutils.sha_strings(lines)
1906
 
            return sha1, sum(map(len, lines)), None
1907
 
        return self._store.add_lines(key, parents, lines,
1908
 
                                     parent_texts=parent_texts,
1909
 
                                     left_matching_blocks=left_matching_blocks,
1910
 
                                     nostore_sha=nostore_sha, random_id=random_id,
1911
 
                                     check_content=check_content)
1912
 
 
1913
 
    def __getattr__(self, name):
1914
 
        return getattr(self._store, name)
1915
 
 
1916
 
 
1917
1627
def network_bytes_to_kind_and_offset(network_bytes):
1918
1628
    """Strip of a record kind from the front of network_bytes.
1919
1629
 
1920
1630
    :param network_bytes: The bytes of a record.
1921
1631
    :return: A tuple (storage_kind, offset_of_remaining_bytes)
1922
1632
    """
1923
 
    line_end = network_bytes.find(b'\n')
1924
 
    storage_kind = network_bytes[:line_end].decode('ascii')
 
1633
    line_end = network_bytes.find('\n')
 
1634
    storage_kind = network_bytes[:line_end]
1925
1635
    return storage_kind, line_end + 1
1926
1636
 
1927
1637
 
1954
1664
        for bytes in self._bytes_iterator:
1955
1665
            storage_kind, line_end = network_bytes_to_kind_and_offset(bytes)
1956
1666
            for record in self._kind_factory[storage_kind](
1957
 
                    storage_kind, bytes, line_end):
 
1667
                storage_kind, bytes, line_end):
1958
1668
                yield record
1959
1669
 
1960
1670
 
1961
1671
def fulltext_network_to_record(kind, bytes, line_end):
1962
1672
    """Convert a network fulltext record to record."""
1963
 
    meta_len, = struct.unpack('!L', bytes[line_end:line_end + 4])
1964
 
    record_meta = bytes[line_end + 4:line_end + 4 + meta_len]
 
1673
    meta_len, = struct.unpack('!L', bytes[line_end:line_end+4])
 
1674
    record_meta = bytes[line_end+4:line_end+4+meta_len]
1965
1675
    key, parents = bencode.bdecode_as_tuple(record_meta)
1966
 
    if parents == b'nil':
 
1676
    if parents == 'nil':
1967
1677
        parents = None
1968
 
    fulltext = bytes[line_end + 4 + meta_len:]
 
1678
    fulltext = bytes[line_end+4+meta_len:]
1969
1679
    return [FulltextContentFactory(key, parents, None, fulltext)]
1970
1680
 
1971
1681
 
1975
1685
 
1976
1686
def record_to_fulltext_bytes(record):
1977
1687
    if record.parents is None:
1978
 
        parents = b'nil'
 
1688
        parents = 'nil'
1979
1689
    else:
1980
1690
        parents = record.parents
1981
1691
    record_meta = bencode.bencode((record.key, parents))
1982
1692
    record_content = record.get_bytes_as('fulltext')
1983
 
    return b"fulltext\n%s%s%s" % (
 
1693
    return "fulltext\n%s%s%s" % (
1984
1694
        _length_prefix(record_meta), record_meta, record_content)
1985
1695
 
1986
1696
 
1995
1705
    # gc-optimal ordering is approximately reverse topological,
1996
1706
    # properly grouped by file-id.
1997
1707
    per_prefix_map = {}
1998
 
    for item in viewitems(parent_map):
 
1708
    for item in parent_map.iteritems():
1999
1709
        key = item[0]
2000
 
        if isinstance(key, bytes) or len(key) == 1:
2001
 
            prefix = b''
 
1710
        if isinstance(key, str) or len(key) == 1:
 
1711
            prefix = ''
2002
1712
        else:
2003
1713
            prefix = key[0]
2004
1714
        try:
2010
1720
    for prefix in sorted(per_prefix_map):
2011
1721
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
2012
1722
    return present_keys
2013
 
 
2014
 
 
2015
 
class _KeyRefs(object):
2016
 
 
2017
 
    def __init__(self, track_new_keys=False):
2018
 
        # dict mapping 'key' to 'set of keys referring to that key'
2019
 
        self.refs = {}
2020
 
        if track_new_keys:
2021
 
            # set remembering all new keys
2022
 
            self.new_keys = set()
2023
 
        else:
2024
 
            self.new_keys = None
2025
 
 
2026
 
    def clear(self):
2027
 
        if self.refs:
2028
 
            self.refs.clear()
2029
 
        if self.new_keys:
2030
 
            self.new_keys.clear()
2031
 
 
2032
 
    def add_references(self, key, refs):
2033
 
        # Record the new references
2034
 
        for referenced in refs:
2035
 
            try:
2036
 
                needed_by = self.refs[referenced]
2037
 
            except KeyError:
2038
 
                needed_by = self.refs[referenced] = set()
2039
 
            needed_by.add(key)
2040
 
        # Discard references satisfied by the new key
2041
 
        self.add_key(key)
2042
 
 
2043
 
    def get_new_keys(self):
2044
 
        return self.new_keys
2045
 
 
2046
 
    def get_unsatisfied_refs(self):
2047
 
        return self.refs.keys()
2048
 
 
2049
 
    def _satisfy_refs_for_key(self, key):
2050
 
        try:
2051
 
            del self.refs[key]
2052
 
        except KeyError:
2053
 
            # No keys depended on this key.  That's ok.
2054
 
            pass
2055
 
 
2056
 
    def add_key(self, key):
2057
 
        # satisfy refs for key, and remember that we've seen this key.
2058
 
        self._satisfy_refs_for_key(key)
2059
 
        if self.new_keys is not None:
2060
 
            self.new_keys.add(key)
2061
 
 
2062
 
    def satisfy_refs_for_keys(self, keys):
2063
 
        for key in keys:
2064
 
            self._satisfy_refs_for_key(key)
2065
 
 
2066
 
    def get_referrers(self):
2067
 
        return set(itertools.chain.from_iterable(viewvalues(self.refs)))