/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: Robert Collins
  • Date: 2010-05-11 08:36:16 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100511083616-b8fjb19zomwupid0
Make all lock methods return Result objects, rather than lock_read returning self, as per John's review.

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')
72
 
 
73
 
 
74
 
class UnavailableRepresentation(errors.InternalBzrError):
75
 
 
76
 
    _fmt = ("The encoding '%(wanted)s' is not available for key %(key)s which "
77
 
            "is encoded as '%(native)s'.")
78
 
 
79
 
    def __init__(self, key, wanted, native):
80
 
        errors.InternalBzrError.__init__(self)
81
 
        self.wanted = wanted
82
 
        self.native = native
83
 
        self.key = key
84
 
 
85
 
 
86
 
class ExistingContent(errors.BzrError):
87
 
 
88
 
    _fmt = "The content being inserted is already present."
 
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')
89
69
 
90
70
 
91
71
class ContentFactory(object):
92
72
    """Abstract interface for insertion and retrieval from a VersionedFile.
93
73
 
94
74
    :ivar sha1: None, or the sha1 of the content fulltext.
95
 
    :ivar size: None, or the size of the content fulltext.
96
75
    :ivar storage_kind: The native storage kind of this factory. One of
97
76
        'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
98
77
        'knit-delta', 'fulltext', 'knit-annotated-ft-gz',
107
86
    def __init__(self):
108
87
        """Create a ContentFactory."""
109
88
        self.sha1 = None
110
 
        self.size = None
111
89
        self.storage_kind = None
112
90
        self.key = None
113
91
        self.parents = None
121
99
    satisfies this, as does a list of lines.
122
100
 
123
101
    :ivar sha1: None, or the sha1 of the content fulltext.
124
 
    :ivar size: None, or the size of the content fulltext.
125
102
    :ivar storage_kind: The native storage kind of this factory. Always
126
103
        'chunked'
127
104
    :ivar key: The key of this content. Each key is a tuple with a single
129
106
    :ivar parents: A tuple of parent keys for self.key. If the object has
130
107
        no parent information, None (as opposed to () for an empty list of
131
108
        parents).
132
 
    :ivar chunks_are_lines: Whether chunks are lines.
133
109
     """
134
110
 
135
 
    def __init__(self, key, parents, sha1, chunks, chunks_are_lines=None):
 
111
    def __init__(self, key, parents, sha1, chunks):
136
112
        """Create a ContentFactory."""
137
113
        self.sha1 = sha1
138
 
        self.size = sum(map(len, chunks))
139
114
        self.storage_kind = 'chunked'
140
115
        self.key = key
141
116
        self.parents = parents
142
117
        self._chunks = chunks
143
 
        self._chunks_are_lines = chunks_are_lines
144
118
 
145
119
    def get_bytes_as(self, storage_kind):
146
120
        if storage_kind == 'chunked':
147
121
            return self._chunks
148
122
        elif storage_kind == 'fulltext':
149
 
            return b''.join(self._chunks)
150
 
        elif storage_kind == 'lines':
151
 
            if self._chunks_are_lines:
152
 
                return self._chunks
153
 
            return list(osutils.chunks_to_lines(self._chunks))
154
 
        raise UnavailableRepresentation(self.key, storage_kind,
155
 
                                        self.storage_kind)
 
123
            return ''.join(self._chunks)
 
124
        raise errors.UnavailableRepresentation(self.key, storage_kind,
 
125
            self.storage_kind)
156
126
 
157
 
    def iter_bytes_as(self, storage_kind):
158
 
        if storage_kind == 'chunked':
159
 
            return iter(self._chunks)
160
 
        elif storage_kind == 'lines':
161
 
            if self._chunks_are_lines:
162
 
                return iter(self._chunks)
163
 
            return iter(osutils.chunks_to_lines(self._chunks))
164
 
        raise UnavailableRepresentation(self.key, storage_kind,
165
 
                                        self.storage_kind)
166
127
 
167
128
class FulltextContentFactory(ContentFactory):
168
129
    """Static data content factory.
183
144
    def __init__(self, key, parents, sha1, text):
184
145
        """Create a ContentFactory."""
185
146
        self.sha1 = sha1
186
 
        self.size = len(text)
187
147
        self.storage_kind = 'fulltext'
188
148
        self.key = key
189
149
        self.parents = parents
190
 
        if not isinstance(text, bytes):
191
 
            raise TypeError(text)
192
150
        self._text = text
193
151
 
194
152
    def get_bytes_as(self, storage_kind):
196
154
            return self._text
197
155
        elif storage_kind == 'chunked':
198
156
            return [self._text]
199
 
        elif storage_kind == 'lines':
200
 
            return osutils.split_lines(self._text)
201
 
        raise UnavailableRepresentation(self.key, storage_kind,
202
 
                                        self.storage_kind)
203
 
 
204
 
    def iter_bytes_as(self, storage_kind):
205
 
        if storage_kind == 'chunked':
206
 
            return iter([self._text])
207
 
        elif storage_kind == 'lines':
208
 
            return iter(osutils.split_lines(self._text))
209
 
        raise UnavailableRepresentation(self.key, storage_kind,
210
 
                                        self.storage_kind)
211
 
 
212
 
 
213
 
class FileContentFactory(ContentFactory):
214
 
    """File-based content factory.
215
 
    """
216
 
 
217
 
    def __init__(self, key, parents, fileobj, sha1=None, size=None):
218
 
        self.key = key
219
 
        self.parents = parents
220
 
        self.file = fileobj
221
 
        self.storage_kind = 'file'
222
 
        self.sha1 = sha1
223
 
        self.size = size
224
 
 
225
 
    def get_bytes_as(self, storage_kind):
226
 
        self.file.seek(0)
227
 
        if storage_kind == 'fulltext':
228
 
            return self.file.read()
229
 
        elif storage_kind == 'chunked':
230
 
            return list(osutils.file_iterator(self.file))
231
 
        elif storage_kind == 'lines':
232
 
            return list(self.file.readlines())
233
 
        raise UnavailableRepresentation(self.key, storage_kind,
234
 
                                        self.storage_kind)
235
 
 
236
 
    def iter_bytes_as(self, storage_kind):
237
 
        self.file.seek(0)
238
 
        if storage_kind == 'chunked':
239
 
            return osutils.file_iterator(self.file)
240
 
        elif storage_kind == 'lines':
241
 
            return self.file
242
 
        raise UnavailableRepresentation(self.key, storage_kind,
243
 
                                        self.storage_kind)
 
157
        raise errors.UnavailableRepresentation(self.key, storage_kind,
 
158
            self.storage_kind)
244
159
 
245
160
 
246
161
class AbsentContentFactory(ContentFactory):
256
171
    def __init__(self, key):
257
172
        """Create a ContentFactory."""
258
173
        self.sha1 = None
259
 
        self.size = None
260
174
        self.storage_kind = 'absent'
261
175
        self.key = key
262
176
        self.parents = None
267
181
                         ' code does not handle if it is missing.'
268
182
                         % (self.key,))
269
183
 
270
 
    def iter_bytes_as(self, storage_kind):
271
 
        raise ValueError('A request was made for key: %s, but that'
272
 
                         ' content is not available, and the calling'
273
 
                         ' code does not handle if it is missing.'
274
 
                         % (self.key,))
275
 
 
276
184
 
277
185
class AdapterFactory(ContentFactory):
278
186
    """A content factory to adapt between key prefix's."""
298
206
            yield record
299
207
 
300
208
 
301
 
class _MPDiffGenerator(object):
302
 
    """Pull out the functionality for generating mp_diffs."""
303
 
 
304
 
    def __init__(self, vf, keys):
305
 
        self.vf = vf
306
 
        # This is the order the keys were requested in
307
 
        self.ordered_keys = tuple(keys)
308
 
        # keys + their parents, what we need to compute the diffs
309
 
        self.needed_keys = ()
310
 
        # Map from key: mp_diff
311
 
        self.diffs = {}
312
 
        # Map from key: parents_needed (may have ghosts)
313
 
        self.parent_map = {}
314
 
        # Parents that aren't present
315
 
        self.ghost_parents = ()
316
 
        # Map from parent_key => number of children for this text
317
 
        self.refcounts = {}
318
 
        # Content chunks that are cached while we still need them
319
 
        self.chunks = {}
320
 
 
321
 
    def _find_needed_keys(self):
322
 
        """Find the set of keys we need to request.
323
 
 
324
 
        This includes all the original keys passed in, and the non-ghost
325
 
        parents of those keys.
326
 
 
327
 
        :return: (needed_keys, refcounts)
328
 
            needed_keys is the set of all texts we need to extract
329
 
            refcounts is a dict of {key: num_children} letting us know when we
330
 
                no longer need to cache a given parent text
331
 
        """
332
 
        # All the keys and their parents
333
 
        needed_keys = set(self.ordered_keys)
334
 
        parent_map = self.vf.get_parent_map(needed_keys)
335
 
        self.parent_map = parent_map
336
 
        # TODO: Should we be using a different construct here? I think this
337
 
        #       uses difference_update internally, and we expect the result to
338
 
        #       be tiny
339
 
        missing_keys = needed_keys.difference(parent_map)
340
 
        if missing_keys:
341
 
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
342
 
        # Parents that might be missing. They are allowed to be ghosts, but we
343
 
        # should check for them
344
 
        refcounts = {}
345
 
        setdefault = refcounts.setdefault
346
 
        just_parents = set()
347
 
        for child_key, parent_keys in viewitems(parent_map):
348
 
            if not parent_keys:
349
 
                # parent_keys may be None if a given VersionedFile claims to
350
 
                # not support graph operations.
351
 
                continue
352
 
            just_parents.update(parent_keys)
353
 
            needed_keys.update(parent_keys)
354
 
            for p in parent_keys:
355
 
                refcounts[p] = setdefault(p, 0) + 1
356
 
        just_parents.difference_update(parent_map)
357
 
        # Remove any parents that are actually ghosts from the needed set
358
 
        self.present_parents = set(self.vf.get_parent_map(just_parents))
359
 
        self.ghost_parents = just_parents.difference(self.present_parents)
360
 
        needed_keys.difference_update(self.ghost_parents)
361
 
        self.needed_keys = needed_keys
362
 
        self.refcounts = refcounts
363
 
        return needed_keys, refcounts
364
 
 
365
 
    def _compute_diff(self, key, parent_lines, lines):
366
 
        """Compute a single mp_diff, and store it in self._diffs"""
367
 
        if len(parent_lines) > 0:
368
 
            # XXX: _extract_blocks is not usefully defined anywhere...
369
 
            #      It was meant to extract the left-parent diff without
370
 
            #      having to recompute it for Knit content (pack-0.92,
371
 
            #      etc). That seems to have regressed somewhere
372
 
            left_parent_blocks = self.vf._extract_blocks(key,
373
 
                                                         parent_lines[0], lines)
374
 
        else:
375
 
            left_parent_blocks = None
376
 
        diff = multiparent.MultiParent.from_lines(lines,
377
 
                                                  parent_lines, left_parent_blocks)
378
 
        self.diffs[key] = diff
379
 
 
380
 
    def _process_one_record(self, key, this_chunks):
381
 
        parent_keys = None
382
 
        if key in self.parent_map:
383
 
            # This record should be ready to diff, since we requested
384
 
            # content in 'topological' order
385
 
            parent_keys = self.parent_map.pop(key)
386
 
            # If a VersionedFile claims 'no-graph' support, then it may return
387
 
            # None for any parent request, so we replace it with an empty tuple
388
 
            if parent_keys is None:
389
 
                parent_keys = ()
390
 
            parent_lines = []
391
 
            for p in parent_keys:
392
 
                # Alternatively we could check p not in self.needed_keys, but
393
 
                # ghost_parents should be tiny versus huge
394
 
                if p in self.ghost_parents:
395
 
                    continue
396
 
                refcount = self.refcounts[p]
397
 
                if refcount == 1:  # Last child reference
398
 
                    self.refcounts.pop(p)
399
 
                    parent_chunks = self.chunks.pop(p)
400
 
                else:
401
 
                    self.refcounts[p] = refcount - 1
402
 
                    parent_chunks = self.chunks[p]
403
 
                p_lines = osutils.chunks_to_lines(parent_chunks)
404
 
                # TODO: Should we cache the line form? We did the
405
 
                #       computation to get it, but storing it this way will
406
 
                #       be less memory efficient...
407
 
                parent_lines.append(p_lines)
408
 
                del p_lines
409
 
            lines = osutils.chunks_to_lines(this_chunks)
410
 
            # Since we needed the lines, we'll go ahead and cache them this way
411
 
            this_chunks = lines
412
 
            self._compute_diff(key, parent_lines, lines)
413
 
            del lines
414
 
        # Is this content required for any more children?
415
 
        if key in self.refcounts:
416
 
            self.chunks[key] = this_chunks
417
 
 
418
 
    def _extract_diffs(self):
419
 
        needed_keys, refcounts = self._find_needed_keys()
420
 
        for record in self.vf.get_record_stream(needed_keys,
421
 
                                                'topological', True):
422
 
            if record.storage_kind == 'absent':
423
 
                raise errors.RevisionNotPresent(record.key, self.vf)
424
 
            self._process_one_record(record.key,
425
 
                                     record.get_bytes_as('chunked'))
426
 
 
427
 
    def compute_diffs(self):
428
 
        self._extract_diffs()
429
 
        dpop = self.diffs.pop
430
 
        return [dpop(k) for k in self.ordered_keys]
431
 
 
432
 
 
433
209
class VersionedFile(object):
434
210
    """Versioned text file storage.
435
211
 
483
259
        raise NotImplementedError
484
260
 
485
261
    def add_lines(self, version_id, parents, lines, parent_texts=None,
486
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
487
 
                  check_content=True):
 
262
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
263
        check_content=True):
488
264
        """Add a single text on top of the versioned file.
489
265
 
490
266
        Must raise RevisionAlreadyPresent if the new version is
524
300
        """
525
301
        self._check_write_ok()
526
302
        return self._add_lines(version_id, parents, lines, parent_texts,
527
 
                               left_matching_blocks, nostore_sha, random_id, check_content)
 
303
            left_matching_blocks, nostore_sha, random_id, check_content)
528
304
 
529
305
    def _add_lines(self, version_id, parents, lines, parent_texts,
530
 
                   left_matching_blocks, nostore_sha, random_id, check_content):
 
306
        left_matching_blocks, nostore_sha, random_id, check_content):
531
307
        """Helper to do the class specific add_lines."""
532
308
        raise NotImplementedError(self.add_lines)
533
309
 
534
310
    def add_lines_with_ghosts(self, version_id, parents, lines,
535
 
                              parent_texts=None, nostore_sha=None, random_id=False,
536
 
                              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):
537
313
        """Add lines to the versioned file, allowing ghosts to be present.
538
314
 
539
315
        This takes the same parameters as add_lines and returns the same.
540
316
        """
541
317
        self._check_write_ok()
542
318
        return self._add_lines_with_ghosts(version_id, parents, lines,
543
 
                                           parent_texts, nostore_sha, random_id, check_content, left_matching_blocks)
 
319
            parent_texts, nostore_sha, random_id, check_content, left_matching_blocks)
544
320
 
545
321
    def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts,
546
 
                               nostore_sha, random_id, check_content, left_matching_blocks):
 
322
        nostore_sha, random_id, check_content, left_matching_blocks):
547
323
        """Helper to do class specific add_lines_with_ghosts."""
548
324
        raise NotImplementedError(self.add_lines_with_ghosts)
549
325
 
554
330
    def _check_lines_not_unicode(self, lines):
555
331
        """Check that lines being added to a versioned file are not unicode."""
556
332
        for line in lines:
557
 
            if not isinstance(line, bytes):
 
333
            if line.__class__ is not str:
558
334
                raise errors.BzrBadParameterUnicode("lines")
559
335
 
560
336
    def _check_lines_are_lines(self, lines):
561
337
        """Check that the lines really are full lines without inline EOL."""
562
338
        for line in lines:
563
 
            if b'\n' in line[:-1]:
 
339
            if '\n' in line[:-1]:
564
340
                raise errors.BzrBadParameterContainsNewline("lines")
565
341
 
566
342
    def get_format_signature(self):
572
348
 
573
349
    def make_mpdiffs(self, version_ids):
574
350
        """Create multiparent diffs for specified versions."""
575
 
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
576
 
        #      is a list of strings, not keys. And while self.get_record_stream
577
 
        #      is supported, it takes *keys*, while self.get_parent_map() takes
578
 
        #      strings... *sigh*
579
351
        knit_versions = set()
580
352
        knit_versions.update(version_ids)
581
353
        parent_map = self.get_parent_map(version_ids)
585
357
            except KeyError:
586
358
                raise errors.RevisionNotPresent(version_id, self)
587
359
        # We need to filter out ghosts, because we can't diff against them.
588
 
        knit_versions = set(self.get_parent_map(knit_versions))
 
360
        knit_versions = set(self.get_parent_map(knit_versions).keys())
589
361
        lines = dict(zip(knit_versions,
590
 
                         self._get_lf_split_line_list(knit_versions)))
 
362
            self._get_lf_split_line_list(knit_versions)))
591
363
        diffs = []
592
364
        for version_id in version_ids:
593
365
            target = lines[version_id]
594
366
            try:
595
367
                parents = [lines[p] for p in parent_map[version_id] if p in
596
 
                           knit_versions]
 
368
                    knit_versions]
597
369
            except KeyError:
598
370
                # I don't know how this could ever trigger.
599
371
                # parent_map[version_id] was already triggered in the previous
606
378
            else:
607
379
                left_parent_blocks = None
608
380
            diffs.append(multiparent.MultiParent.from_lines(target, parents,
609
 
                                                            left_parent_blocks))
 
381
                         left_parent_blocks))
610
382
        return diffs
611
383
 
612
384
    def _extract_blocks(self, version_id, source, target):
629
401
        for version, parent_ids, expected_sha1, mpdiff in records:
630
402
            needed_parents.update(p for p in parent_ids
631
403
                                  if not mpvf.has_version(p))
632
 
        present_parents = set(self.get_parent_map(needed_parents))
 
404
        present_parents = set(self.get_parent_map(needed_parents).keys())
633
405
        for parent_id, lines in zip(present_parents,
634
 
                                    self._get_lf_split_line_list(present_parents)):
 
406
                                 self._get_lf_split_line_list(present_parents)):
635
407
            mpvf.add_version(lines, parent_id, [])
636
 
        for (version, parent_ids, expected_sha1, mpdiff), lines in zip(
637
 
                records, mpvf.get_line_list(versions)):
 
408
        for (version, parent_ids, expected_sha1, mpdiff), lines in\
 
409
            zip(records, mpvf.get_line_list(versions)):
638
410
            if len(parent_ids) == 1:
639
411
                left_matching_blocks = list(mpdiff.get_matching_blocks(0,
640
 
                                                                       mpvf.get_diff(parent_ids[0]).num_lines()))
 
412
                    mpvf.get_diff(parent_ids[0]).num_lines()))
641
413
            else:
642
414
                left_matching_blocks = None
643
415
            try:
644
416
                _, _, version_text = self.add_lines_with_ghosts(version,
645
 
                                                                parent_ids, lines, vf_parents,
646
 
                                                                left_matching_blocks=left_matching_blocks)
 
417
                    parent_ids, lines, vf_parents,
 
418
                    left_matching_blocks=left_matching_blocks)
647
419
            except NotImplementedError:
648
420
                # The vf can't handle ghosts, so add lines normally, which will
649
421
                # (reasonably) fail if there are ghosts in the data.
650
422
                _, _, version_text = self.add_lines(version,
651
 
                                                    parent_ids, lines, vf_parents,
652
 
                                                    left_matching_blocks=left_matching_blocks)
 
423
                    parent_ids, lines, vf_parents,
 
424
                    left_matching_blocks=left_matching_blocks)
653
425
            vf_parents[version] = version_text
654
426
        sha1s = self.get_sha1s(versions)
655
427
        for version, parent_ids, expected_sha1, mpdiff in records:
662
434
        Raises RevisionNotPresent if version is not present in
663
435
        file history.
664
436
        """
665
 
        return b''.join(self.get_lines(version_id))
 
437
        return ''.join(self.get_lines(version_id))
666
438
    get_string = get_text
667
439
 
668
440
    def get_texts(self, version_ids):
671
443
        Raises RevisionNotPresent if version is not present in
672
444
        file history.
673
445
        """
674
 
        return [b''.join(self.get_lines(v)) for v in version_ids]
 
446
        return [''.join(self.get_lines(v)) for v in version_ids]
675
447
 
676
448
    def get_lines(self, version_id):
677
449
        """Return version contents as a sequence of lines.
682
454
        raise NotImplementedError(self.get_lines)
683
455
 
684
456
    def _get_lf_split_line_list(self, version_ids):
685
 
        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)]
686
458
 
687
 
    def get_ancestry(self, version_ids):
 
459
    def get_ancestry(self, version_ids, topo_sorted=True):
688
460
        """Return a list of all ancestors of given version(s). This
689
461
        will not include the null revision.
690
462
 
 
463
        This list will not be topologically sorted if topo_sorted=False is
 
464
        passed.
 
465
 
691
466
        Must raise RevisionNotPresent if any of the given versions are
692
467
        not present in file history."""
 
468
        if isinstance(version_ids, basestring):
 
469
            version_ids = [version_ids]
693
470
        raise NotImplementedError(self.get_ancestry)
694
471
 
695
472
    def get_ancestry_with_ghosts(self, version_ids):
756
533
        """
757
534
        raise NotImplementedError(self.iter_lines_added_or_present_in_versions)
758
535
 
759
 
    def plan_merge(self, ver_a, ver_b, base=None):
 
536
    def plan_merge(self, ver_a, ver_b):
760
537
        """Return pseudo-annotation indicating how the two versions merge.
761
538
 
762
539
        This is computed between versions a and b and their common
801
578
        self.calls = []
802
579
 
803
580
    def add_lines(self, key, parents, lines, parent_texts=None,
804
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
805
 
                  check_content=True):
 
581
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
582
        check_content=True):
806
583
        self.calls.append(("add_lines", key, parents, lines, parent_texts,
807
 
                           left_matching_blocks, nostore_sha, random_id, check_content))
 
584
            left_matching_blocks, nostore_sha, random_id, check_content))
808
585
        return self._backing_vf.add_lines(key, parents, lines, parent_texts,
809
 
                                          left_matching_blocks, nostore_sha, random_id, check_content)
810
 
 
811
 
    def add_content(self, factory, parent_texts=None,
812
 
                    left_matching_blocks=None, nostore_sha=None, random_id=False,
813
 
                    check_content=True):
814
 
        self.calls.append(("add_content", factory, parent_texts,
815
 
                           left_matching_blocks, nostore_sha, random_id, check_content))
816
 
        return self._backing_vf.add_content(
817
 
            factory, parent_texts, left_matching_blocks, nostore_sha,
818
 
            random_id, check_content)
 
586
            left_matching_blocks, nostore_sha, random_id, check_content)
819
587
 
820
588
    def check(self):
821
589
        self._backing_vf.check()
826
594
 
827
595
    def get_record_stream(self, keys, sort_order, include_delta_closure):
828
596
        self.calls.append(("get_record_stream", list(keys), sort_order,
829
 
                           include_delta_closure))
 
597
            include_delta_closure))
830
598
        return self._backing_vf.get_record_stream(keys, sort_order,
831
 
                                                  include_delta_closure)
 
599
            include_delta_closure)
832
600
 
833
601
    def get_sha1s(self, keys):
834
602
        self.calls.append(("get_sha1s", copy(keys)))
865
633
 
866
634
    def get_record_stream(self, keys, sort_order, include_delta_closure):
867
635
        self.calls.append(("get_record_stream", list(keys), sort_order,
868
 
                           include_delta_closure))
 
636
            include_delta_closure))
869
637
        if sort_order == 'unordered':
870
638
            def sort_key(key):
871
639
                return (self._key_priority.get(key, 0), key)
873
641
            # backing_vf
874
642
            for key in sorted(keys, key=sort_key):
875
643
                for record in self._backing_vf.get_record_stream([key],
876
 
                                                                 'unordered', include_delta_closure):
 
644
                                'unordered', include_delta_closure):
877
645
                    yield record
878
646
        else:
879
647
            for record in self._backing_vf.get_record_stream(keys, sort_order,
880
 
                                                             include_delta_closure):
 
648
                            include_delta_closure):
881
649
                yield record
882
650
 
883
651
 
887
655
    def map(self, key):
888
656
        """Map key to an underlying storage identifier.
889
657
 
890
 
        :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').
891
659
        :return: An underlying storage identifier, specific to the partitioning
892
660
            mechanism.
893
661
        """
924
692
 
925
693
    def map(self, key):
926
694
        """See KeyMapper.map()."""
927
 
        return urlutils.quote(self._map(key))
 
695
        return urllib.quote(self._map(key))
928
696
 
929
697
    def unmap(self, partition_id):
930
698
        """See KeyMapper.unmap()."""
931
 
        return self._unmap(urlutils.unquote(partition_id))
 
699
        return self._unmap(urllib.unquote(partition_id))
932
700
 
933
701
 
934
702
class PrefixMapper(URLEscapeMapper):
939
707
 
940
708
    def _map(self, key):
941
709
        """See KeyMapper.map()."""
942
 
        return key[0].decode('utf-8')
 
710
        return key[0]
943
711
 
944
712
    def _unmap(self, partition_id):
945
713
        """See KeyMapper.unmap()."""
946
 
        return (partition_id.encode('utf-8'),)
 
714
        return (partition_id,)
947
715
 
948
716
 
949
717
class HashPrefixMapper(URLEscapeMapper):
955
723
    def _map(self, key):
956
724
        """See KeyMapper.map()."""
957
725
        prefix = self._escape(key[0])
958
 
        return "%02x/%s" % (adler32(prefix) & 0xff, prefix.decode('utf-8'))
 
726
        return "%02x/%s" % (adler32(prefix) & 0xff, prefix)
959
727
 
960
728
    def _escape(self, prefix):
961
729
        """No escaping needed here."""
963
731
 
964
732
    def _unmap(self, partition_id):
965
733
        """See KeyMapper.unmap()."""
966
 
        return (self._unescape(osutils.basename(partition_id)).encode('utf-8'),)
 
734
        return (self._unescape(osutils.basename(partition_id)),)
967
735
 
968
736
    def _unescape(self, basename):
969
737
        """No unescaping needed for HashPrefixMapper."""
976
744
    This mapper is for use with a transport based backend.
977
745
    """
978
746
 
979
 
    _safe = bytearray(b"abcdefghijklmnopqrstuvwxyz0123456789-_@,.")
 
747
    _safe = "abcdefghijklmnopqrstuvwxyz0123456789-_@,."
980
748
 
981
749
    def _escape(self, prefix):
982
750
        """Turn a key element into a filesystem safe string.
983
751
 
984
 
        This is similar to a plain urlutils.quote, except
 
752
        This is similar to a plain urllib.quote, except
985
753
        it uses specific safe characters, so that it doesn't
986
754
        have to translate a lot of valid file ids.
987
755
        """
988
756
        # @ does not get escaped. This is because it is a valid
989
757
        # filesystem character we use all the time, and it looks
990
758
        # a lot better than seeing %40 all the time.
991
 
        r = [(c in self._safe) and chr(c) or ('%%%02x' % c)
992
 
             for c in bytearray(prefix)]
993
 
        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)
994
762
 
995
763
    def _unescape(self, basename):
996
764
        """Escaped names are easily unescaped by urlutils."""
997
 
        return urlutils.unquote(basename)
 
765
        return urllib.unquote(basename)
998
766
 
999
767
 
1000
768
def make_versioned_files_factory(versioned_file_factory, mapper):
1006
774
    """
1007
775
    def factory(transport):
1008
776
        return ThunkedVersionedFiles(transport, versioned_file_factory, mapper,
1009
 
                                     lambda: True)
 
777
            lambda:True)
1010
778
    return factory
1011
779
 
1012
780
 
1021
789
 
1022
790
    The keyspace is expressed via simple tuples. Any instance of VersionedFiles
1023
791
    may have a different length key-size, but that size will be constant for
1024
 
    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
1025
793
    instances with a key-size of 2 for storing user files in a repository, with
1026
794
    the first element the fileid, and the second the version of that file.
1027
795
 
1028
796
    The use of tuples allows a single code base to support several different
1029
797
    uses with only the mapping logic changing from instance to instance.
1030
 
 
1031
 
    :ivar _immediate_fallback_vfs: For subclasses that support stacking,
1032
 
        this is a list of other VersionedFiles immediately underneath this
1033
 
        one.  They may in turn each have further fallbacks.
1034
798
    """
1035
799
 
1036
800
    def add_lines(self, key, parents, lines, parent_texts=None,
1037
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
1038
 
                  check_content=True):
 
801
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
802
        check_content=True):
1039
803
        """Add a text to the store.
1040
804
 
1041
805
        :param key: The key tuple of the text to add. If the last element is
1072
836
        """
1073
837
        raise NotImplementedError(self.add_lines)
1074
838
 
1075
 
    def add_content(self, factory, parent_texts=None,
1076
 
                    left_matching_blocks=None, nostore_sha=None, random_id=False,
1077
 
                    check_content=True):
1078
 
        """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.
1079
843
 
1080
844
        :param key: The key tuple of the text to add. If the last element is
1081
845
            None, a CHK string will be generated during the addition.
1082
846
        :param parents: The parents key tuples of the text to add.
1083
 
        :param chunk_iter: An iterable over bytestrings.
1084
 
        :param parent_texts: An optional dictionary containing the opaque
1085
 
            representations of some or all of the parents of version_id to
1086
 
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
1087
 
            returned by add_lines or data corruption can be caused.
1088
 
        :param left_matching_blocks: a hint about which areas are common
1089
 
            between the text and its left-hand-parent.  The format is
1090
 
            the SequenceMatcher.get_matching_blocks format.
 
847
        :param text: A string containing the text to be committed.
1091
848
        :param nostore_sha: Raise ExistingContent and do not add the lines to
1092
849
            the versioned file if the digest of the lines matches this.
1093
850
        :param random_id: If True a random id has been selected rather than
1100
857
            bytestrings that are correctly formed lines.
1101
858
        :return: The text sha1, the number of bytes in the text, and an opaque
1102
859
                 representation of the inserted version which can be provided
1103
 
                 back to future add_lines calls in the parent_texts dictionary.
 
860
                 back to future _add_text calls in the parent_texts dictionary.
1104
861
        """
1105
 
        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)
1106
868
 
1107
869
    def add_mpdiffs(self, records):
1108
870
        """Add mpdiffs to this VersionedFile.
1122
884
                                  if not mpvf.has_version(p))
1123
885
        # It seems likely that adding all the present parents as fulltexts can
1124
886
        # easily exhaust memory.
 
887
        chunks_to_lines = osutils.chunks_to_lines
1125
888
        for record in self.get_record_stream(needed_parents, 'unordered',
1126
 
                                             True):
 
889
            True):
1127
890
            if record.storage_kind == 'absent':
1128
891
                continue
1129
 
            mpvf.add_version(record.get_bytes_as('lines'), record.key, [])
1130
 
        for (key, parent_keys, expected_sha1, mpdiff), lines in zip(
1131
 
                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)):
1132
896
            if len(parent_keys) == 1:
1133
897
                left_matching_blocks = list(mpdiff.get_matching_blocks(0,
1134
 
                                                                       mpvf.get_diff(parent_keys[0]).num_lines()))
 
898
                    mpvf.get_diff(parent_keys[0]).num_lines()))
1135
899
            else:
1136
900
                left_matching_blocks = None
1137
901
            version_sha1, _, version_text = self.add_lines(key,
1138
 
                                                           parent_keys, lines, vf_parents,
1139
 
                                                           left_matching_blocks=left_matching_blocks)
 
902
                parent_keys, lines, vf_parents,
 
903
                left_matching_blocks=left_matching_blocks)
1140
904
            if version_sha1 != expected_sha1:
1141
905
                raise errors.VersionedFileInvalidChecksum(version)
1142
906
            vf_parents[key] = version_text
1150
914
 
1151
915
    def check(self, progress_bar=None):
1152
916
        """Check this object for integrity.
1153
 
 
 
917
        
1154
918
        :param progress_bar: A progress bar to output as the check progresses.
1155
919
        :param keys: Specific keys within the VersionedFiles to check. When
1156
920
            this parameter is not None, check() becomes a generator as per
1175
939
    def _check_lines_not_unicode(self, lines):
1176
940
        """Check that lines being added to a versioned file are not unicode."""
1177
941
        for line in lines:
1178
 
            if line.__class__ is not bytes:
 
942
            if line.__class__ is not str:
1179
943
                raise errors.BzrBadParameterUnicode("lines")
1180
944
 
1181
945
    def _check_lines_are_lines(self, lines):
1182
946
        """Check that the lines really are full lines without inline EOL."""
1183
947
        for line in lines:
1184
 
            if b'\n' in line[:-1]:
 
948
            if '\n' in line[:-1]:
1185
949
                raise errors.BzrBadParameterContainsNewline("lines")
1186
950
 
1187
951
    def get_known_graph_ancestry(self, keys):
1192
956
        while pending:
1193
957
            this_parent_map = self.get_parent_map(pending)
1194
958
            parent_map.update(this_parent_map)
1195
 
            pending = set(itertools.chain.from_iterable(
1196
 
                viewvalues(this_parent_map)))
1197
 
            pending.difference_update(parent_map)
 
959
            pending = set()
 
960
            map(pending.update, this_parent_map.itervalues())
 
961
            pending = pending.difference(parent_map)
1198
962
        kg = _mod_graph.KnownGraph(parent_map)
1199
963
        return kg
1200
964
 
1231
995
        """
1232
996
        raise NotImplementedError(self.get_sha1s)
1233
997
 
1234
 
    __contains__ = index._has_key_from_parent_map
 
998
    has_key = index._has_key_from_parent_map
1235
999
 
1236
1000
    def get_missing_compression_parent_keys(self):
1237
1001
        """Return an iterable of keys of missing compression parents.
1283
1047
 
1284
1048
    def make_mpdiffs(self, keys):
1285
1049
        """Create multiparent diffs for specified keys."""
1286
 
        generator = _MPDiffGenerator(self, keys)
1287
 
        return generator.compute_diffs()
1288
 
 
1289
 
    def get_annotator(self):
1290
 
        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
1291
1087
 
1292
1088
    missing_keys = index._missing_keys_from_parent_map
1293
1089
 
1294
1090
    def _extract_blocks(self, version_id, source, target):
1295
1091
        return None
1296
1092
 
1297
 
    def _transitive_fallbacks(self):
1298
 
        """Return the whole stack of fallback versionedfiles.
1299
 
 
1300
 
        This VersionedFiles may have a list of fallbacks, but it doesn't
1301
 
        necessarily know about the whole stack going down, and it can't know
1302
 
        at open time because they may change after the objects are opened.
1303
 
        """
1304
 
        all_fallbacks = []
1305
 
        for a_vfs in self._immediate_fallback_vfs:
1306
 
            all_fallbacks.append(a_vfs)
1307
 
            all_fallbacks.extend(a_vfs._transitive_fallbacks())
1308
 
        return all_fallbacks
1309
 
 
1310
1093
 
1311
1094
class ThunkedVersionedFiles(VersionedFiles):
1312
1095
    """Storage for many versioned files thunked onto a 'VersionedFile' class.
1325
1108
        self._mapper = mapper
1326
1109
        self._is_locked = is_locked
1327
1110
 
1328
 
    def add_content(self, factory, parent_texts=None,
1329
 
                    left_matching_blocks=None, nostore_sha=None, random_id=False):
1330
 
        """See VersionedFiles.add_content()."""
1331
 
        lines = factory.get_bytes_as('lines')
1332
 
        return self.add_lines(
1333
 
            factory.key, factory.parents, lines,
1334
 
            parent_texts=parent_texts,
1335
 
            left_matching_blocks=left_matching_blocks,
1336
 
            nostore_sha=nostore_sha,
1337
 
            random_id=random_id,
1338
 
            check_content=True)
1339
 
 
1340
1111
    def add_lines(self, key, parents, lines, parent_texts=None,
1341
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
1342
 
                  check_content=True):
 
1112
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
1113
        check_content=True):
1343
1114
        """See VersionedFiles.add_lines()."""
1344
1115
        path = self._mapper.map(key)
1345
1116
        version_id = key[-1]
1348
1119
        try:
1349
1120
            try:
1350
1121
                return vf.add_lines_with_ghosts(version_id, parents, lines,
1351
 
                                                parent_texts=parent_texts,
1352
 
                                                left_matching_blocks=left_matching_blocks,
1353
 
                                                nostore_sha=nostore_sha, random_id=random_id,
1354
 
                                                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)
1355
1126
            except NotImplementedError:
1356
1127
                return vf.add_lines(version_id, parents, lines,
1357
 
                                    parent_texts=parent_texts,
1358
 
                                    left_matching_blocks=left_matching_blocks,
1359
 
                                    nostore_sha=nostore_sha, random_id=random_id,
1360
 
                                    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)
1361
1132
        except errors.NoSuchFile:
1362
1133
            # parent directory may be missing, try again.
1363
1134
            self._transport.mkdir(osutils.dirname(path))
1364
1135
            try:
1365
1136
                return vf.add_lines_with_ghosts(version_id, parents, lines,
1366
 
                                                parent_texts=parent_texts,
1367
 
                                                left_matching_blocks=left_matching_blocks,
1368
 
                                                nostore_sha=nostore_sha, random_id=random_id,
1369
 
                                                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)
1370
1141
            except NotImplementedError:
1371
1142
                return vf.add_lines(version_id, parents, lines,
1372
 
                                    parent_texts=parent_texts,
1373
 
                                    left_matching_blocks=left_matching_blocks,
1374
 
                                    nostore_sha=nostore_sha, random_id=random_id,
1375
 
                                    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)
1376
1147
 
1377
1148
    def annotate(self, key):
1378
1149
        """Return a list of (version-key, line) tuples for the text of key.
1388
1159
            result.append((prefix + (origin,), line))
1389
1160
        return result
1390
1161
 
 
1162
    def get_annotator(self):
 
1163
        return annotate.Annotator(self)
 
1164
 
1391
1165
    def check(self, progress_bar=None, keys=None):
1392
1166
        """See VersionedFiles.check()."""
1393
1167
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1394
 
        # 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 
1395
1169
        # have the older VersiondFile interface updated too.
1396
1170
        for prefix, vf in self._iter_all_components():
1397
1171
            vf.check()
1407
1181
        """
1408
1182
        prefixes = self._partition_keys(keys)
1409
1183
        result = {}
1410
 
        for prefix, suffixes in viewitems(prefixes):
 
1184
        for prefix, suffixes in prefixes.items():
1411
1185
            path = self._mapper.map(prefix)
1412
1186
            vf = self._get_vf(path)
1413
1187
            parent_map = vf.get_parent_map(suffixes)
1414
 
            for key, parents in viewitems(parent_map):
 
1188
            for key, parents in parent_map.items():
1415
1189
                result[prefix + (key,)] = tuple(
1416
1190
                    prefix + (parent,) for parent in parents)
1417
1191
        return result
1420
1194
        if not self._is_locked():
1421
1195
            raise errors.ObjectNotLocked(self)
1422
1196
        return self._file_factory(path, self._transport, create=True,
1423
 
                                  get_scope=lambda: None)
 
1197
            get_scope=lambda:None)
1424
1198
 
1425
1199
    def _partition_keys(self, keys):
1426
1200
        """Turn keys into a dict of prefix:suffix_list."""
1430
1204
            prefix_keys.append(key[-1])
1431
1205
        return result
1432
1206
 
1433
 
    def _iter_all_prefixes(self):
 
1207
    def _get_all_prefixes(self):
1434
1208
        # Identify all key prefixes.
1435
1209
        # XXX: A bit hacky, needs polish.
1436
 
        if isinstance(self._mapper, ConstantMapper):
 
1210
        if type(self._mapper) == ConstantMapper:
1437
1211
            paths = [self._mapper.map(())]
1438
1212
            prefixes = [()]
1439
1213
        else:
1453
1227
        for prefix, suffixes, vf in self._iter_keys_vf(keys):
1454
1228
            suffixes = [(suffix,) for suffix in suffixes]
1455
1229
            for record in vf.get_record_stream(suffixes, ordering,
1456
 
                                               include_delta_closure):
 
1230
                include_delta_closure):
1457
1231
                if record.parents is not None:
1458
1232
                    record.parents = tuple(
1459
1233
                        prefix + parent for parent in record.parents)
1463
1237
    def _iter_keys_vf(self, keys):
1464
1238
        prefixes = self._partition_keys(keys)
1465
1239
        sha1s = {}
1466
 
        for prefix, suffixes in viewitems(prefixes):
 
1240
        for prefix, suffixes in prefixes.items():
1467
1241
            path = self._mapper.map(prefix)
1468
1242
            vf = self._get_vf(path)
1469
1243
            yield prefix, suffixes, vf
1471
1245
    def get_sha1s(self, keys):
1472
1246
        """See VersionedFiles.get_sha1s()."""
1473
1247
        sha1s = {}
1474
 
        for prefix, suffixes, vf in self._iter_keys_vf(keys):
 
1248
        for prefix,suffixes, vf in self._iter_keys_vf(keys):
1475
1249
            vf_sha1s = vf.get_sha1s(suffixes)
1476
 
            for suffix, sha1 in viewitems(vf_sha1s):
 
1250
            for suffix, sha1 in vf_sha1s.iteritems():
1477
1251
                sha1s[prefix + (suffix,)] = sha1
1478
1252
        return sha1s
1479
1253
 
1525
1299
                yield line, prefix + (version,)
1526
1300
 
1527
1301
    def _iter_all_components(self):
1528
 
        for path, prefix in self._iter_all_prefixes():
 
1302
        for path, prefix in self._get_all_prefixes():
1529
1303
            yield prefix, self._get_vf(path)
1530
1304
 
1531
1305
    def keys(self):
1537
1311
        return result
1538
1312
 
1539
1313
 
1540
 
class VersionedFilesWithFallbacks(VersionedFiles):
1541
 
 
1542
 
    def without_fallbacks(self):
1543
 
        """Return a clone of this object without any fallbacks configured."""
1544
 
        raise NotImplementedError(self.without_fallbacks)
1545
 
 
1546
 
    def add_fallback_versioned_files(self, a_versioned_files):
1547
 
        """Add a source of texts for texts not present in this knit.
1548
 
 
1549
 
        :param a_versioned_files: A VersionedFiles object.
1550
 
        """
1551
 
        raise NotImplementedError(self.add_fallback_versioned_files)
1552
 
 
1553
 
    def get_known_graph_ancestry(self, keys):
1554
 
        """Get a KnownGraph instance with the ancestry of keys."""
1555
 
        parent_map, missing_keys = self._index.find_ancestry(keys)
1556
 
        for fallback in self._transitive_fallbacks():
1557
 
            if not missing_keys:
1558
 
                break
1559
 
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
1560
 
                missing_keys)
1561
 
            parent_map.update(f_parent_map)
1562
 
            missing_keys = f_missing_keys
1563
 
        kg = _mod_graph.KnownGraph(parent_map)
1564
 
        return kg
1565
 
 
1566
 
 
1567
1314
class _PlanMergeVersionedFile(VersionedFiles):
1568
1315
    """A VersionedFile for uncommitted and committed texts.
1569
1316
 
1590
1337
        # line data for locally held keys.
1591
1338
        self._lines = {}
1592
1339
        # key lookup providers
1593
 
        self._providers = [_mod_graph.DictParentsProvider(self._parents)]
 
1340
        self._providers = [DictParentsProvider(self._parents)]
1594
1341
 
1595
1342
    def plan_merge(self, ver_a, ver_b, base=None):
1596
1343
        """See VersionedFile.plan_merge"""
1597
 
        from ..merge import _PlanMerge
 
1344
        from bzrlib.merge import _PlanMerge
1598
1345
        if base is None:
1599
1346
            return _PlanMerge(ver_a, ver_b, self, (self._file_id,)).plan_merge()
1600
 
        old_plan = list(_PlanMerge(ver_a, base, self,
1601
 
                                   (self._file_id,)).plan_merge())
1602
 
        new_plan = list(_PlanMerge(ver_a, ver_b, self,
1603
 
                                   (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())
1604
1349
        return _PlanMerge._subtract_plans(old_plan, new_plan)
1605
1350
 
1606
1351
    def plan_lca_merge(self, ver_a, ver_b, base=None):
1607
 
        from ..merge import _PlanLCAMerge
1608
 
        graph = _mod_graph.Graph(self)
1609
 
        new_plan = _PlanLCAMerge(
1610
 
            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()
1611
1355
        if base is None:
1612
1356
            return new_plan
1613
 
        old_plan = _PlanLCAMerge(
1614
 
            ver_a, base, self, (self._file_id,), graph).plan_merge()
 
1357
        old_plan = _PlanLCAMerge(ver_a, base, self, (self._file_id,), graph).plan_merge()
1615
1358
        return _PlanLCAMerge._subtract_plans(list(old_plan), list(new_plan))
1616
1359
 
1617
 
    def add_content(self, factory):
1618
 
        return self.add_lines(
1619
 
            factory.key, factory.parents, factory.get_bytes_as('lines'))
1620
 
 
1621
1360
    def add_lines(self, key, parents, lines):
1622
1361
        """See VersionedFiles.add_lines
1623
1362
 
1624
1363
        Lines are added locally, not to fallback versionedfiles.  Also, ghosts
1625
1364
        are permitted.  Only reserved ids are permitted.
1626
1365
        """
1627
 
        if not isinstance(key, tuple):
 
1366
        if type(key) is not tuple:
1628
1367
            raise TypeError(key)
1629
1368
        if not revision.is_reserved_id(key[-1]):
1630
1369
            raise ValueError('Only reserved ids may be used')
1642
1381
                lines = self._lines[key]
1643
1382
                parents = self._parents[key]
1644
1383
                pending.remove(key)
1645
 
                yield ChunkedContentFactory(
1646
 
                    key, parents, None, lines,
1647
 
                    chunks_are_lines=True)
 
1384
                yield ChunkedContentFactory(key, parents, None, lines)
1648
1385
        for versionedfile in self.fallback_versionedfiles:
1649
1386
            for record in versionedfile.get_record_stream(
1650
 
                    pending, 'unordered', True):
 
1387
                pending, 'unordered', True):
1651
1388
                if record.storage_kind == 'absent':
1652
1389
                    continue
1653
1390
                else:
1671
1408
            result[revision.NULL_REVISION] = ()
1672
1409
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1673
1410
        result.update(
1674
 
            _mod_graph.StackedParentsProvider(
1675
 
                self._providers).get_parent_map(keys))
1676
 
        for key, parents in viewitems(result):
 
1411
            StackedParentsProvider(self._providers).get_parent_map(keys))
 
1412
        for key, parents in result.iteritems():
1677
1413
            if parents == ():
1678
1414
                result[key] = (revision.NULL_REVISION,)
1679
1415
        return result
1748
1484
                ch_b = ch_a = True
1749
1485
            else:
1750
1486
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
1751
 
                                 'killed-base'):
 
1487
                        'killed-base'):
1752
1488
                    raise AssertionError(state)
1753
1489
        for struct in outstanding_struct():
1754
1490
            yield struct
1812
1548
    """Weave merge that takes a VersionedFile and two versions as its input."""
1813
1549
 
1814
1550
    def __init__(self, versionedfile, ver_a, ver_b,
1815
 
                 a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
 
1551
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1816
1552
        plan = versionedfile.plan_merge(ver_a, ver_b)
1817
1553
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1818
1554
 
1852
1588
 
1853
1589
    def get_parent_map(self, keys):
1854
1590
        """See VersionedFiles.get_parent_map."""
1855
 
        parent_view = viewitems(self._get_parent_map(k for (k,) in keys))
1856
 
        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()])
1857
1593
 
1858
1594
    def get_sha1s(self, keys):
1859
1595
        """See VersionedFiles.get_sha1s."""
1873
1609
            if lines is not None:
1874
1610
                if not isinstance(lines, list):
1875
1611
                    raise AssertionError
1876
 
                yield ChunkedContentFactory(
1877
 
                    (k,), None, sha1=osutils.sha_strings(lines),
1878
 
                    chunks=lines, chunks_are_lines=True)
 
1612
                yield ChunkedContentFactory((k,), None,
 
1613
                        sha1=osutils.sha_strings(lines),
 
1614
                        chunks=lines)
1879
1615
            else:
1880
1616
                yield AbsentContentFactory((k,))
1881
1617
 
1888
1624
                yield (l, key)
1889
1625
 
1890
1626
 
1891
 
class NoDupeAddLinesDecorator(object):
1892
 
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
1893
 
    is already present.
1894
 
    """
1895
 
 
1896
 
    def __init__(self, store):
1897
 
        self._store = store
1898
 
 
1899
 
    def add_lines(self, key, parents, lines, parent_texts=None,
1900
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
1901
 
                  check_content=True):
1902
 
        """See VersionedFiles.add_lines.
1903
 
 
1904
 
        This implementation may return None as the third element of the return
1905
 
        value when the original store wouldn't.
1906
 
        """
1907
 
        if nostore_sha:
1908
 
            raise NotImplementedError(
1909
 
                "NoDupeAddLinesDecorator.add_lines does not implement the "
1910
 
                "nostore_sha behaviour.")
1911
 
        if key[-1] is None:
1912
 
            sha1 = osutils.sha_strings(lines)
1913
 
            key = (b"sha1:" + sha1,)
1914
 
        else:
1915
 
            sha1 = None
1916
 
        if key in self._store.get_parent_map([key]):
1917
 
            # This key has already been inserted, so don't do it again.
1918
 
            if sha1 is None:
1919
 
                sha1 = osutils.sha_strings(lines)
1920
 
            return sha1, sum(map(len, lines)), None
1921
 
        return self._store.add_lines(key, parents, lines,
1922
 
                                     parent_texts=parent_texts,
1923
 
                                     left_matching_blocks=left_matching_blocks,
1924
 
                                     nostore_sha=nostore_sha, random_id=random_id,
1925
 
                                     check_content=check_content)
1926
 
 
1927
 
    def __getattr__(self, name):
1928
 
        return getattr(self._store, name)
1929
 
 
1930
 
 
1931
1627
def network_bytes_to_kind_and_offset(network_bytes):
1932
1628
    """Strip of a record kind from the front of network_bytes.
1933
1629
 
1934
1630
    :param network_bytes: The bytes of a record.
1935
1631
    :return: A tuple (storage_kind, offset_of_remaining_bytes)
1936
1632
    """
1937
 
    line_end = network_bytes.find(b'\n')
1938
 
    storage_kind = network_bytes[:line_end].decode('ascii')
 
1633
    line_end = network_bytes.find('\n')
 
1634
    storage_kind = network_bytes[:line_end]
1939
1635
    return storage_kind, line_end + 1
1940
1636
 
1941
1637
 
1968
1664
        for bytes in self._bytes_iterator:
1969
1665
            storage_kind, line_end = network_bytes_to_kind_and_offset(bytes)
1970
1666
            for record in self._kind_factory[storage_kind](
1971
 
                    storage_kind, bytes, line_end):
 
1667
                storage_kind, bytes, line_end):
1972
1668
                yield record
1973
1669
 
1974
1670
 
1975
1671
def fulltext_network_to_record(kind, bytes, line_end):
1976
1672
    """Convert a network fulltext record to record."""
1977
 
    meta_len, = struct.unpack('!L', bytes[line_end:line_end + 4])
1978
 
    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]
1979
1675
    key, parents = bencode.bdecode_as_tuple(record_meta)
1980
 
    if parents == b'nil':
 
1676
    if parents == 'nil':
1981
1677
        parents = None
1982
 
    fulltext = bytes[line_end + 4 + meta_len:]
 
1678
    fulltext = bytes[line_end+4+meta_len:]
1983
1679
    return [FulltextContentFactory(key, parents, None, fulltext)]
1984
1680
 
1985
1681
 
1989
1685
 
1990
1686
def record_to_fulltext_bytes(record):
1991
1687
    if record.parents is None:
1992
 
        parents = b'nil'
 
1688
        parents = 'nil'
1993
1689
    else:
1994
1690
        parents = record.parents
1995
1691
    record_meta = bencode.bencode((record.key, parents))
1996
1692
    record_content = record.get_bytes_as('fulltext')
1997
 
    return b"fulltext\n%s%s%s" % (
 
1693
    return "fulltext\n%s%s%s" % (
1998
1694
        _length_prefix(record_meta), record_meta, record_content)
1999
1695
 
2000
1696
 
2009
1705
    # gc-optimal ordering is approximately reverse topological,
2010
1706
    # properly grouped by file-id.
2011
1707
    per_prefix_map = {}
2012
 
    for item in viewitems(parent_map):
 
1708
    for item in parent_map.iteritems():
2013
1709
        key = item[0]
2014
 
        if isinstance(key, bytes) or len(key) == 1:
2015
 
            prefix = b''
 
1710
        if isinstance(key, str) or len(key) == 1:
 
1711
            prefix = ''
2016
1712
        else:
2017
1713
            prefix = key[0]
2018
1714
        try:
2024
1720
    for prefix in sorted(per_prefix_map):
2025
1721
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
2026
1722
    return present_keys
2027
 
 
2028
 
 
2029
 
class _KeyRefs(object):
2030
 
 
2031
 
    def __init__(self, track_new_keys=False):
2032
 
        # dict mapping 'key' to 'set of keys referring to that key'
2033
 
        self.refs = {}
2034
 
        if track_new_keys:
2035
 
            # set remembering all new keys
2036
 
            self.new_keys = set()
2037
 
        else:
2038
 
            self.new_keys = None
2039
 
 
2040
 
    def clear(self):
2041
 
        if self.refs:
2042
 
            self.refs.clear()
2043
 
        if self.new_keys:
2044
 
            self.new_keys.clear()
2045
 
 
2046
 
    def add_references(self, key, refs):
2047
 
        # Record the new references
2048
 
        for referenced in refs:
2049
 
            try:
2050
 
                needed_by = self.refs[referenced]
2051
 
            except KeyError:
2052
 
                needed_by = self.refs[referenced] = set()
2053
 
            needed_by.add(key)
2054
 
        # Discard references satisfied by the new key
2055
 
        self.add_key(key)
2056
 
 
2057
 
    def get_new_keys(self):
2058
 
        return self.new_keys
2059
 
 
2060
 
    def get_unsatisfied_refs(self):
2061
 
        return self.refs.keys()
2062
 
 
2063
 
    def _satisfy_refs_for_key(self, key):
2064
 
        try:
2065
 
            del self.refs[key]
2066
 
        except KeyError:
2067
 
            # No keys depended on this key.  That's ok.
2068
 
            pass
2069
 
 
2070
 
    def add_key(self, key):
2071
 
        # satisfy refs for key, and remember that we've seen this key.
2072
 
        self._satisfy_refs_for_key(key)
2073
 
        if self.new_keys is not None:
2074
 
            self.new_keys.add(key)
2075
 
 
2076
 
    def satisfy_refs_for_keys(self, keys):
2077
 
        for key in keys:
2078
 
            self._satisfy_refs_for_key(key)
2079
 
 
2080
 
    def get_referrers(self):
2081
 
        return set(itertools.chain.from_iterable(viewvalues(self.refs)))