~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

(igc) zc.buildout Windows build support (Sidnei da Silva)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# Authors:
4
4
#   Johan Rydberg <jrydberg@gnu.org>
15
15
#
16
16
# You should have received a copy of the GNU General Public License
17
17
# along with this program; if not, write to the Free Software
18
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
19
 
20
20
"""Versioned text file storage api."""
21
21
 
 
22
from copy import copy
 
23
from cStringIO import StringIO
 
24
import os
 
25
import struct
 
26
from zlib import adler32
 
27
 
22
28
from bzrlib.lazy_import import lazy_import
23
29
lazy_import(globals(), """
 
30
import urllib
24
31
 
25
32
from bzrlib import (
 
33
    annotate,
26
34
    errors,
 
35
    groupcompress,
 
36
    index,
 
37
    knit,
27
38
    osutils,
 
39
    multiparent,
28
40
    tsort,
29
41
    revision,
30
42
    ui,
31
43
    )
 
44
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
32
45
from bzrlib.transport.memory import MemoryTransport
33
46
""")
34
 
 
35
47
from bzrlib.inter import InterObject
 
48
from bzrlib.registry import Registry
 
49
from bzrlib.symbol_versioning import *
36
50
from bzrlib.textmerge import TextMerge
37
 
from bzrlib.symbol_versioning import (deprecated_function,
38
 
        deprecated_method,
39
 
        zero_eight,
40
 
        )
 
51
from bzrlib import bencode
 
52
 
 
53
 
 
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
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'knit-delta-gz'),
 
60
    'bzrlib.knit', 'DeltaAnnotatedToUnannotated')
 
61
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'fulltext'),
 
62
    'bzrlib.knit', 'DeltaAnnotatedToFullText')
 
63
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'knit-ft-gz'),
 
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')
 
69
 
 
70
 
 
71
class ContentFactory(object):
 
72
    """Abstract interface for insertion and retrieval from a VersionedFile.
 
73
 
 
74
    :ivar sha1: None, or the sha1 of the content fulltext.
 
75
    :ivar storage_kind: The native storage kind of this factory. One of
 
76
        'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
 
77
        'knit-delta', 'fulltext', 'knit-annotated-ft-gz',
 
78
        'knit-annotated-delta-gz', 'knit-ft-gz', 'knit-delta-gz'.
 
79
    :ivar key: The key of this content. Each key is a tuple with a single
 
80
        string in it.
 
81
    :ivar parents: A tuple of parent keys for self.key. If the object has
 
82
        no parent information, None (as opposed to () for an empty list of
 
83
        parents).
 
84
    """
 
85
 
 
86
    def __init__(self):
 
87
        """Create a ContentFactory."""
 
88
        self.sha1 = None
 
89
        self.storage_kind = None
 
90
        self.key = None
 
91
        self.parents = None
 
92
 
 
93
 
 
94
class ChunkedContentFactory(ContentFactory):
 
95
    """Static data content factory.
 
96
 
 
97
    This takes a 'chunked' list of strings. The only requirement on 'chunked' is
 
98
    that ''.join(lines) becomes a valid fulltext. A tuple of a single string
 
99
    satisfies this, as does a list of lines.
 
100
 
 
101
    :ivar sha1: None, or the sha1 of the content fulltext.
 
102
    :ivar storage_kind: The native storage kind of this factory. Always
 
103
        'chunked'
 
104
    :ivar key: The key of this content. Each key is a tuple with a single
 
105
        string in it.
 
106
    :ivar parents: A tuple of parent keys for self.key. If the object has
 
107
        no parent information, None (as opposed to () for an empty list of
 
108
        parents).
 
109
     """
 
110
 
 
111
    def __init__(self, key, parents, sha1, chunks):
 
112
        """Create a ContentFactory."""
 
113
        self.sha1 = sha1
 
114
        self.storage_kind = 'chunked'
 
115
        self.key = key
 
116
        self.parents = parents
 
117
        self._chunks = chunks
 
118
 
 
119
    def get_bytes_as(self, storage_kind):
 
120
        if storage_kind == 'chunked':
 
121
            return self._chunks
 
122
        elif storage_kind == 'fulltext':
 
123
            return ''.join(self._chunks)
 
124
        raise errors.UnavailableRepresentation(self.key, storage_kind,
 
125
            self.storage_kind)
 
126
 
 
127
 
 
128
class FulltextContentFactory(ContentFactory):
 
129
    """Static data content factory.
 
130
 
 
131
    This takes a fulltext when created and just returns that during
 
132
    get_bytes_as('fulltext').
 
133
 
 
134
    :ivar sha1: None, or the sha1 of the content fulltext.
 
135
    :ivar storage_kind: The native storage kind of this factory. Always
 
136
        'fulltext'.
 
137
    :ivar key: The key of this content. Each key is a tuple with a single
 
138
        string in it.
 
139
    :ivar parents: A tuple of parent keys for self.key. If the object has
 
140
        no parent information, None (as opposed to () for an empty list of
 
141
        parents).
 
142
     """
 
143
 
 
144
    def __init__(self, key, parents, sha1, text):
 
145
        """Create a ContentFactory."""
 
146
        self.sha1 = sha1
 
147
        self.storage_kind = 'fulltext'
 
148
        self.key = key
 
149
        self.parents = parents
 
150
        self._text = text
 
151
 
 
152
    def get_bytes_as(self, storage_kind):
 
153
        if storage_kind == self.storage_kind:
 
154
            return self._text
 
155
        elif storage_kind == 'chunked':
 
156
            return [self._text]
 
157
        raise errors.UnavailableRepresentation(self.key, storage_kind,
 
158
            self.storage_kind)
 
159
 
 
160
 
 
161
class AbsentContentFactory(ContentFactory):
 
162
    """A placeholder content factory for unavailable texts.
 
163
 
 
164
    :ivar sha1: None.
 
165
    :ivar storage_kind: 'absent'.
 
166
    :ivar key: The key of this content. Each key is a tuple with a single
 
167
        string in it.
 
168
    :ivar parents: None.
 
169
    """
 
170
 
 
171
    def __init__(self, key):
 
172
        """Create a ContentFactory."""
 
173
        self.sha1 = None
 
174
        self.storage_kind = 'absent'
 
175
        self.key = key
 
176
        self.parents = None
 
177
 
 
178
 
 
179
class AdapterFactory(ContentFactory):
 
180
    """A content factory to adapt between key prefix's."""
 
181
 
 
182
    def __init__(self, key, parents, adapted):
 
183
        """Create an adapter factory instance."""
 
184
        self.key = key
 
185
        self.parents = parents
 
186
        self._adapted = adapted
 
187
 
 
188
    def __getattr__(self, attr):
 
189
        """Return a member from the adapted object."""
 
190
        if attr in ('key', 'parents'):
 
191
            return self.__dict__[attr]
 
192
        else:
 
193
            return getattr(self._adapted, attr)
 
194
 
 
195
 
 
196
def filter_absent(record_stream):
 
197
    """Adapt a record stream to remove absent records."""
 
198
    for record in record_stream:
 
199
        if record.storage_kind != 'absent':
 
200
            yield record
41
201
 
42
202
 
43
203
class VersionedFile(object):
44
204
    """Versioned text file storage.
45
 
    
 
205
 
46
206
    A versioned file manages versions of line-based text files,
47
207
    keeping track of the originating version for each line.
48
208
 
54
214
    Texts are identified by a version-id string.
55
215
    """
56
216
 
57
 
    def __init__(self, access_mode):
58
 
        self.finished = False
59
 
        self._access_mode = access_mode
60
 
 
61
217
    @staticmethod
62
218
    def check_not_reserved_id(version_id):
63
219
        revision.check_not_reserved_id(version_id)
66
222
        """Copy this versioned file to name on transport."""
67
223
        raise NotImplementedError(self.copy_to)
68
224
 
69
 
    @deprecated_method(zero_eight)
70
 
    def names(self):
71
 
        """Return a list of all the versions in this versioned file.
 
225
    def get_record_stream(self, versions, ordering, include_delta_closure):
 
226
        """Get a stream of records for versions.
72
227
 
73
 
        Please use versionedfile.versions() now.
 
228
        :param versions: The versions to include. Each version is a tuple
 
229
            (version,).
 
230
        :param ordering: Either 'unordered' or 'topological'. A topologically
 
231
            sorted stream has compression parents strictly before their
 
232
            children.
 
233
        :param include_delta_closure: If True then the closure across any
 
234
            compression parents will be included (in the data content of the
 
235
            stream, not in the emitted records). This guarantees that
 
236
            'fulltext' can be used successfully on every record.
 
237
        :return: An iterator of ContentFactory objects, each of which is only
 
238
            valid until the iterator is advanced.
74
239
        """
75
 
        return self.versions()
76
 
 
77
 
    def versions(self):
78
 
        """Return a unsorted list of versions."""
79
 
        raise NotImplementedError(self.versions)
80
 
 
81
 
    def has_ghost(self, version_id):
82
 
        """Returns whether version is present as a ghost."""
83
 
        raise NotImplementedError(self.has_ghost)
 
240
        raise NotImplementedError(self.get_record_stream)
84
241
 
85
242
    def has_version(self, version_id):
86
243
        """Returns whether version is present."""
87
244
        raise NotImplementedError(self.has_version)
88
245
 
89
 
    def add_delta(self, version_id, parents, delta_parent, sha1, noeol, delta):
90
 
        """Add a text to the versioned file via a pregenerated delta.
91
 
 
92
 
        :param version_id: The version id being added.
93
 
        :param parents: The parents of the version_id.
94
 
        :param delta_parent: The parent this delta was created against.
95
 
        :param sha1: The sha1 of the full text.
96
 
        :param delta: The delta instructions. See get_delta for details.
97
 
        """
98
 
        version_id = osutils.safe_revision_id(version_id)
99
 
        parents = [osutils.safe_revision_id(v) for v in parents]
100
 
        self._check_write_ok()
101
 
        if self.has_version(version_id):
102
 
            raise errors.RevisionAlreadyPresent(version_id, self)
103
 
        return self._add_delta(version_id, parents, delta_parent, sha1, noeol, delta)
104
 
 
105
 
    def _add_delta(self, version_id, parents, delta_parent, sha1, noeol, delta):
106
 
        """Class specific routine to add a delta.
107
 
 
108
 
        This generic version simply applies the delta to the delta_parent and
109
 
        then inserts it.
110
 
        """
111
 
        # strip annotation from delta
112
 
        new_delta = []
113
 
        for start, stop, delta_len, delta_lines in delta:
114
 
            new_delta.append((start, stop, delta_len, [text for origin, text in delta_lines]))
115
 
        if delta_parent is not None:
116
 
            parent_full = self.get_lines(delta_parent)
117
 
        else:
118
 
            parent_full = []
119
 
        new_full = self._apply_delta(parent_full, new_delta)
120
 
        # its impossible to have noeol on an empty file
121
 
        if noeol and new_full[-1][-1] == '\n':
122
 
            new_full[-1] = new_full[-1][:-1]
123
 
        self.add_lines(version_id, parents, new_full)
124
 
 
125
 
    def add_lines(self, version_id, parents, lines, parent_texts=None):
 
246
    def insert_record_stream(self, stream):
 
247
        """Insert a record stream into this versioned file.
 
248
 
 
249
        :param stream: A stream of records to insert.
 
250
        :return: None
 
251
        :seealso VersionedFile.get_record_stream:
 
252
        """
 
253
        raise NotImplementedError
 
254
 
 
255
    def add_lines(self, version_id, parents, lines, parent_texts=None,
 
256
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
257
        check_content=True):
126
258
        """Add a single text on top of the versioned file.
127
259
 
128
260
        Must raise RevisionAlreadyPresent if the new version is
130
262
 
131
263
        Must raise RevisionNotPresent if any of the given parents are
132
264
        not present in file history.
133
 
        :param parent_texts: An optional dictionary containing the opaque 
134
 
             representations of some or all of the parents of 
135
 
             version_id to allow delta optimisations. 
136
 
             VERY IMPORTANT: the texts must be those returned
137
 
             by add_lines or data corruption can be caused.
138
 
        :return: An opaque representation of the inserted version which can be
139
 
                 provided back to future add_lines calls in the parent_texts
140
 
                 dictionary.
 
265
 
 
266
        :param lines: A list of lines. Each line must be a bytestring. And all
 
267
            of them except the last must be terminated with \n and contain no
 
268
            other \n's. The last line may either contain no \n's or a single
 
269
            terminated \n. If the lines list does meet this constraint the add
 
270
            routine may error or may succeed - but you will be unable to read
 
271
            the data back accurately. (Checking the lines have been split
 
272
            correctly is expensive and extremely unlikely to catch bugs so it
 
273
            is not done at runtime unless check_content is True.)
 
274
        :param parent_texts: An optional dictionary containing the opaque
 
275
            representations of some or all of the parents of version_id to
 
276
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
 
277
            returned by add_lines or data corruption can be caused.
 
278
        :param left_matching_blocks: a hint about which areas are common
 
279
            between the text and its left-hand-parent.  The format is
 
280
            the SequenceMatcher.get_matching_blocks format.
 
281
        :param nostore_sha: Raise ExistingContent and do not add the lines to
 
282
            the versioned file if the digest of the lines matches this.
 
283
        :param random_id: If True a random id has been selected rather than
 
284
            an id determined by some deterministic process such as a converter
 
285
            from a foreign VCS. When True the backend may choose not to check
 
286
            for uniqueness of the resulting key within the versioned file, so
 
287
            this should only be done when the result is expected to be unique
 
288
            anyway.
 
289
        :param check_content: If True, the lines supplied are verified to be
 
290
            bytestrings that are correctly formed lines.
 
291
        :return: The text sha1, the number of bytes in the text, and an opaque
 
292
                 representation of the inserted version which can be provided
 
293
                 back to future add_lines calls in the parent_texts dictionary.
141
294
        """
142
 
        version_id = osutils.safe_revision_id(version_id)
143
 
        parents = [osutils.safe_revision_id(v) for v in parents]
144
295
        self._check_write_ok()
145
 
        return self._add_lines(version_id, parents, lines, parent_texts)
 
296
        return self._add_lines(version_id, parents, lines, parent_texts,
 
297
            left_matching_blocks, nostore_sha, random_id, check_content)
146
298
 
147
 
    def _add_lines(self, version_id, parents, lines, parent_texts):
 
299
    def _add_lines(self, version_id, parents, lines, parent_texts,
 
300
        left_matching_blocks, nostore_sha, random_id, check_content):
148
301
        """Helper to do the class specific add_lines."""
149
302
        raise NotImplementedError(self.add_lines)
150
303
 
151
304
    def add_lines_with_ghosts(self, version_id, parents, lines,
152
 
                              parent_texts=None):
 
305
        parent_texts=None, nostore_sha=None, random_id=False,
 
306
        check_content=True, left_matching_blocks=None):
153
307
        """Add lines to the versioned file, allowing ghosts to be present.
154
 
        
155
 
        This takes the same parameters as add_lines.
 
308
 
 
309
        This takes the same parameters as add_lines and returns the same.
156
310
        """
157
 
        version_id = osutils.safe_revision_id(version_id)
158
 
        parents = [osutils.safe_revision_id(v) for v in parents]
159
311
        self._check_write_ok()
160
312
        return self._add_lines_with_ghosts(version_id, parents, lines,
161
 
                                           parent_texts)
 
313
            parent_texts, nostore_sha, random_id, check_content, left_matching_blocks)
162
314
 
163
 
    def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts):
 
315
    def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts,
 
316
        nostore_sha, random_id, check_content, left_matching_blocks):
164
317
        """Helper to do class specific add_lines_with_ghosts."""
165
318
        raise NotImplementedError(self.add_lines_with_ghosts)
166
319
 
180
333
            if '\n' in line[:-1]:
181
334
                raise errors.BzrBadParameterContainsNewline("lines")
182
335
 
183
 
    def _check_write_ok(self):
184
 
        """Is the versioned file marked as 'finished' ? Raise if it is."""
185
 
        if self.finished:
186
 
            raise errors.OutSideTransaction()
187
 
        if self._access_mode != 'w':
188
 
            raise errors.ReadOnlyObjectDirtiedError(self)
189
 
 
190
 
    def enable_cache(self):
191
 
        """Tell this versioned file that it should cache any data it reads.
192
 
        
193
 
        This is advisory, implementations do not have to support caching.
194
 
        """
195
 
        pass
196
 
    
197
 
    def clear_cache(self):
198
 
        """Remove any data cached in the versioned file object.
199
 
 
200
 
        This only needs to be supported if caches are supported
201
 
        """
202
 
        pass
203
 
 
204
 
    def clone_text(self, new_version_id, old_version_id, parents):
205
 
        """Add an identical text to old_version_id as new_version_id.
206
 
 
207
 
        Must raise RevisionNotPresent if the old version or any of the
208
 
        parents are not present in file history.
209
 
 
210
 
        Must raise RevisionAlreadyPresent if the new version is
211
 
        already present in file history."""
212
 
        new_version_id = osutils.safe_revision_id(new_version_id)
213
 
        old_version_id = osutils.safe_revision_id(old_version_id)
214
 
        self._check_write_ok()
215
 
        return self._clone_text(new_version_id, old_version_id, parents)
216
 
 
217
 
    def _clone_text(self, new_version_id, old_version_id, parents):
218
 
        """Helper function to do the _clone_text work."""
219
 
        raise NotImplementedError(self.clone_text)
220
 
 
221
 
    def create_empty(self, name, transport, mode=None):
222
 
        """Create a new versioned file of this exact type.
223
 
 
224
 
        :param name: the file name
225
 
        :param transport: the transport
226
 
        :param mode: optional file mode.
227
 
        """
228
 
        raise NotImplementedError(self.create_empty)
229
 
 
230
 
    def fix_parents(self, version_id, new_parents):
231
 
        """Fix the parents list for version.
232
 
        
233
 
        This is done by appending a new version to the index
234
 
        with identical data except for the parents list.
235
 
        the parents list must be a superset of the current
236
 
        list.
237
 
        """
238
 
        version_id = osutils.safe_revision_id(version_id)
239
 
        new_parents = [osutils.safe_revision_id(p) for p in new_parents]
240
 
        self._check_write_ok()
241
 
        return self._fix_parents(version_id, new_parents)
242
 
 
243
 
    def _fix_parents(self, version_id, new_parents):
244
 
        """Helper for fix_parents."""
245
 
        raise NotImplementedError(self.fix_parents)
246
 
 
247
 
    def get_delta(self, version):
248
 
        """Get a delta for constructing version from some other version.
249
 
        
250
 
        :return: (delta_parent, sha1, noeol, delta)
251
 
        Where delta_parent is a version id or None to indicate no parent.
252
 
        """
253
 
        raise NotImplementedError(self.get_delta)
254
 
 
255
 
    def get_deltas(self, version_ids):
256
 
        """Get multiple deltas at once for constructing versions.
257
 
        
258
 
        :return: dict(version_id:(delta_parent, sha1, noeol, delta))
259
 
        Where delta_parent is a version id or None to indicate no parent, and
260
 
        version_id is the version_id created by that delta.
261
 
        """
262
 
        result = {}
263
 
        for version_id in version_ids:
264
 
            result[version_id] = self.get_delta(version_id)
265
 
        return result
266
 
 
267
 
    def get_sha1(self, version_id):
268
 
        """Get the stored sha1 sum for the given revision.
269
 
        
270
 
        :param name: The name of the version to lookup
271
 
        """
272
 
        raise NotImplementedError(self.get_sha1)
273
 
 
274
 
    def get_suffixes(self):
275
 
        """Return the file suffixes associated with this versioned file."""
276
 
        raise NotImplementedError(self.get_suffixes)
277
 
    
 
336
    def get_format_signature(self):
 
337
        """Get a text description of the data encoding in this file.
 
338
 
 
339
        :since: 0.90
 
340
        """
 
341
        raise NotImplementedError(self.get_format_signature)
 
342
 
 
343
    def make_mpdiffs(self, version_ids):
 
344
        """Create multiparent diffs for specified versions."""
 
345
        knit_versions = set()
 
346
        knit_versions.update(version_ids)
 
347
        parent_map = self.get_parent_map(version_ids)
 
348
        for version_id in version_ids:
 
349
            try:
 
350
                knit_versions.update(parent_map[version_id])
 
351
            except KeyError:
 
352
                raise errors.RevisionNotPresent(version_id, self)
 
353
        # We need to filter out ghosts, because we can't diff against them.
 
354
        knit_versions = set(self.get_parent_map(knit_versions).keys())
 
355
        lines = dict(zip(knit_versions,
 
356
            self._get_lf_split_line_list(knit_versions)))
 
357
        diffs = []
 
358
        for version_id in version_ids:
 
359
            target = lines[version_id]
 
360
            try:
 
361
                parents = [lines[p] for p in parent_map[version_id] if p in
 
362
                    knit_versions]
 
363
            except KeyError:
 
364
                # I don't know how this could ever trigger.
 
365
                # parent_map[version_id] was already triggered in the previous
 
366
                # for loop, and lines[p] has the 'if p in knit_versions' check,
 
367
                # so we again won't have a KeyError.
 
368
                raise errors.RevisionNotPresent(version_id, self)
 
369
            if len(parents) > 0:
 
370
                left_parent_blocks = self._extract_blocks(version_id,
 
371
                                                          parents[0], target)
 
372
            else:
 
373
                left_parent_blocks = None
 
374
            diffs.append(multiparent.MultiParent.from_lines(target, parents,
 
375
                         left_parent_blocks))
 
376
        return diffs
 
377
 
 
378
    def _extract_blocks(self, version_id, source, target):
 
379
        return None
 
380
 
 
381
    def add_mpdiffs(self, records):
 
382
        """Add mpdiffs to this VersionedFile.
 
383
 
 
384
        Records should be iterables of version, parents, expected_sha1,
 
385
        mpdiff. mpdiff should be a MultiParent instance.
 
386
        """
 
387
        # Does this need to call self._check_write_ok()? (IanC 20070919)
 
388
        vf_parents = {}
 
389
        mpvf = multiparent.MultiMemoryVersionedFile()
 
390
        versions = []
 
391
        for version, parent_ids, expected_sha1, mpdiff in records:
 
392
            versions.append(version)
 
393
            mpvf.add_diff(mpdiff, version, parent_ids)
 
394
        needed_parents = set()
 
395
        for version, parent_ids, expected_sha1, mpdiff in records:
 
396
            needed_parents.update(p for p in parent_ids
 
397
                                  if not mpvf.has_version(p))
 
398
        present_parents = set(self.get_parent_map(needed_parents).keys())
 
399
        for parent_id, lines in zip(present_parents,
 
400
                                 self._get_lf_split_line_list(present_parents)):
 
401
            mpvf.add_version(lines, parent_id, [])
 
402
        for (version, parent_ids, expected_sha1, mpdiff), lines in\
 
403
            zip(records, mpvf.get_line_list(versions)):
 
404
            if len(parent_ids) == 1:
 
405
                left_matching_blocks = list(mpdiff.get_matching_blocks(0,
 
406
                    mpvf.get_diff(parent_ids[0]).num_lines()))
 
407
            else:
 
408
                left_matching_blocks = None
 
409
            try:
 
410
                _, _, version_text = self.add_lines_with_ghosts(version,
 
411
                    parent_ids, lines, vf_parents,
 
412
                    left_matching_blocks=left_matching_blocks)
 
413
            except NotImplementedError:
 
414
                # The vf can't handle ghosts, so add lines normally, which will
 
415
                # (reasonably) fail if there are ghosts in the data.
 
416
                _, _, version_text = self.add_lines(version,
 
417
                    parent_ids, lines, vf_parents,
 
418
                    left_matching_blocks=left_matching_blocks)
 
419
            vf_parents[version] = version_text
 
420
        sha1s = self.get_sha1s(versions)
 
421
        for version, parent_ids, expected_sha1, mpdiff in records:
 
422
            if expected_sha1 != sha1s[version]:
 
423
                raise errors.VersionedFileInvalidChecksum(version)
 
424
 
278
425
    def get_text(self, version_id):
279
426
        """Return version contents as a text string.
280
427
 
300
447
        """
301
448
        raise NotImplementedError(self.get_lines)
302
449
 
 
450
    def _get_lf_split_line_list(self, version_ids):
 
451
        return [StringIO(t).readlines() for t in self.get_texts(version_ids)]
 
452
 
303
453
    def get_ancestry(self, version_ids, topo_sorted=True):
304
454
        """Return a list of all ancestors of given version(s). This
305
455
        will not include the null revision.
312
462
        if isinstance(version_ids, basestring):
313
463
            version_ids = [version_ids]
314
464
        raise NotImplementedError(self.get_ancestry)
315
 
        
 
465
 
316
466
    def get_ancestry_with_ghosts(self, version_ids):
317
467
        """Return a list of all ancestors of given version(s). This
318
468
        will not include the null revision.
319
469
 
320
470
        Must raise RevisionNotPresent if any of the given versions are
321
471
        not present in file history.
322
 
        
 
472
 
323
473
        Ghosts that are known about will be included in ancestry list,
324
474
        but are not explicitly marked.
325
475
        """
326
476
        raise NotImplementedError(self.get_ancestry_with_ghosts)
327
 
        
328
 
    def get_graph(self, version_ids=None):
329
 
        """Return a graph from the versioned file. 
330
 
        
331
 
        Ghosts are not listed or referenced in the graph.
332
 
        :param version_ids: Versions to select.
333
 
                            None means retrieve all versions.
334
 
        """
335
 
        result = {}
336
 
        if version_ids is None:
337
 
            for version in self.versions():
338
 
                result[version] = self.get_parents(version)
339
 
        else:
340
 
            pending = set(osutils.safe_revision_id(v) for v in version_ids)
341
 
            while pending:
342
 
                version = pending.pop()
343
 
                if version in result:
344
 
                    continue
345
 
                parents = self.get_parents(version)
346
 
                for parent in parents:
347
 
                    if parent in result:
348
 
                        continue
349
 
                    pending.add(parent)
350
 
                result[version] = parents
351
 
        return result
352
 
 
353
 
    def get_graph_with_ghosts(self):
354
 
        """Return a graph for the entire versioned file.
355
 
        
356
 
        Ghosts are referenced in parents list but are not
357
 
        explicitly listed.
358
 
        """
359
 
        raise NotImplementedError(self.get_graph_with_ghosts)
360
 
 
361
 
    @deprecated_method(zero_eight)
362
 
    def parent_names(self, version):
363
 
        """Return version names for parents of a version.
364
 
        
365
 
        See get_parents for the current api.
366
 
        """
367
 
        return self.get_parents(version)
368
 
 
369
 
    def get_parents(self, version_id):
370
 
        """Return version names for parents of a version.
371
 
 
372
 
        Must raise RevisionNotPresent if version is not present in
373
 
        file history.
374
 
        """
375
 
        raise NotImplementedError(self.get_parents)
 
477
 
 
478
    def get_parent_map(self, version_ids):
 
479
        """Get a map of the parents of version_ids.
 
480
 
 
481
        :param version_ids: The version ids to look up parents for.
 
482
        :return: A mapping from version id to parents.
 
483
        """
 
484
        raise NotImplementedError(self.get_parent_map)
376
485
 
377
486
    def get_parents_with_ghosts(self, version_id):
378
487
        """Return version names for parents of version_id.
383
492
        Ghosts that are known about will be included in the parent list,
384
493
        but are not explicitly marked.
385
494
        """
386
 
        raise NotImplementedError(self.get_parents_with_ghosts)
387
 
 
388
 
    def annotate_iter(self, version_id):
389
 
        """Yield list of (version-id, line) pairs for the specified
390
 
        version.
391
 
 
392
 
        Must raise RevisionNotPresent if any of the given versions are
393
 
        not present in file history.
394
 
        """
395
 
        raise NotImplementedError(self.annotate_iter)
 
495
        try:
 
496
            return list(self.get_parent_map([version_id])[version_id])
 
497
        except KeyError:
 
498
            raise errors.RevisionNotPresent(version_id, self)
396
499
 
397
500
    def annotate(self, version_id):
398
 
        return list(self.annotate_iter(version_id))
399
 
 
400
 
    def _apply_delta(self, lines, delta):
401
 
        """Apply delta to lines."""
402
 
        lines = list(lines)
403
 
        offset = 0
404
 
        for start, end, count, delta_lines in delta:
405
 
            lines[offset+start:offset+end] = delta_lines
406
 
            offset = offset + (start - end) + count
407
 
        return lines
408
 
 
409
 
    def join(self, other, pb=None, msg=None, version_ids=None,
410
 
             ignore_missing=False):
411
 
        """Integrate versions from other into this versioned file.
412
 
 
413
 
        If version_ids is None all versions from other should be
414
 
        incorporated into this versioned file.
415
 
 
416
 
        Must raise RevisionNotPresent if any of the specified versions
417
 
        are not present in the other files history unless ignore_missing
418
 
        is supplied when they are silently skipped.
 
501
        """Return a list of (version-id, line) tuples for version_id.
 
502
 
 
503
        :raise RevisionNotPresent: If the given version is
 
504
        not present in file history.
419
505
        """
420
 
        self._check_write_ok()
421
 
        return InterVersionedFile.get(other, self).join(
422
 
            pb,
423
 
            msg,
424
 
            version_ids,
425
 
            ignore_missing)
 
506
        raise NotImplementedError(self.annotate)
426
507
 
427
 
    def iter_lines_added_or_present_in_versions(self, version_ids=None, 
 
508
    def iter_lines_added_or_present_in_versions(self, version_ids=None,
428
509
                                                pb=None):
429
510
        """Iterate over the lines in the versioned file from version_ids.
430
511
 
431
 
        This may return lines from other versions, and does not return the
432
 
        specific version marker at this point. The api may be changed
433
 
        during development to include the version that the versioned file
434
 
        thinks is relevant, but given that such hints are just guesses,
435
 
        its better not to have it if we don't need it.
 
512
        This may return lines from other versions. Each item the returned
 
513
        iterator yields is a tuple of a line and a text version that that line
 
514
        is present in (not introduced in).
 
515
 
 
516
        Ordering of results is in whatever order is most suitable for the
 
517
        underlying storage format.
436
518
 
437
519
        If a progress bar is supplied, it may be used to indicate progress.
438
520
        The caller is responsible for cleaning up progress bars (because this
440
522
 
441
523
        NOTES: Lines are normalised: they will all have \n terminators.
442
524
               Lines are returned in arbitrary order.
 
525
 
 
526
        :return: An iterator over (line, version_id).
443
527
        """
444
528
        raise NotImplementedError(self.iter_lines_added_or_present_in_versions)
445
529
 
446
 
    def transaction_finished(self):
447
 
        """The transaction that this file was opened in has finished.
448
 
 
449
 
        This records self.finished = True and should cause all mutating
450
 
        operations to error.
451
 
        """
452
 
        self.finished = True
453
 
 
454
 
    @deprecated_method(zero_eight)
455
 
    def walk(self, version_ids=None):
456
 
        """Walk the versioned file as a weave-like structure, for
457
 
        versions relative to version_ids.  Yields sequence of (lineno,
458
 
        insert, deletes, text) for each relevant line.
459
 
 
460
 
        Must raise RevisionNotPresent if any of the specified versions
461
 
        are not present in the file history.
462
 
 
463
 
        :param version_ids: the version_ids to walk with respect to. If not
464
 
                            supplied the entire weave-like structure is walked.
465
 
 
466
 
        walk is deprecated in favour of iter_lines_added_or_present_in_versions
467
 
        """
468
 
        raise NotImplementedError(self.walk)
469
 
 
470
 
    @deprecated_method(zero_eight)
471
 
    def iter_names(self):
472
 
        """Walk the names list."""
473
 
        return iter(self.versions())
474
 
 
475
530
    def plan_merge(self, ver_a, ver_b):
476
531
        """Return pseudo-annotation indicating how the two versions merge.
477
532
 
488
543
        unchanged   Alive in both a and b (possibly created in both)
489
544
        new-a       Created in a
490
545
        new-b       Created in b
491
 
        ghost-a     Killed in a, unborn in b    
 
546
        ghost-a     Killed in a, unborn in b
492
547
        ghost-b     Killed in b, unborn in a
493
548
        irrelevant  Not in either revision
494
549
        """
495
550
        raise NotImplementedError(VersionedFile.plan_merge)
496
 
        
 
551
 
497
552
    def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
498
553
                    b_marker=TextMerge.B_MARKER):
499
554
        return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
500
555
 
501
556
 
 
557
class RecordingVersionedFilesDecorator(object):
 
558
    """A minimal versioned files that records calls made on it.
 
559
 
 
560
    Only enough methods have been added to support tests using it to date.
 
561
 
 
562
    :ivar calls: A list of the calls made; can be reset at any time by
 
563
        assigning [] to it.
 
564
    """
 
565
 
 
566
    def __init__(self, backing_vf):
 
567
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
 
568
 
 
569
        :param backing_vf: The versioned file to answer all methods.
 
570
        """
 
571
        self._backing_vf = backing_vf
 
572
        self.calls = []
 
573
 
 
574
    def add_lines(self, key, parents, lines, parent_texts=None,
 
575
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
576
        check_content=True):
 
577
        self.calls.append(("add_lines", key, parents, lines, parent_texts,
 
578
            left_matching_blocks, nostore_sha, random_id, check_content))
 
579
        return self._backing_vf.add_lines(key, parents, lines, parent_texts,
 
580
            left_matching_blocks, nostore_sha, random_id, check_content)
 
581
 
 
582
    def check(self):
 
583
        self._backing_vf.check()
 
584
 
 
585
    def get_parent_map(self, keys):
 
586
        self.calls.append(("get_parent_map", copy(keys)))
 
587
        return self._backing_vf.get_parent_map(keys)
 
588
 
 
589
    def get_record_stream(self, keys, sort_order, include_delta_closure):
 
590
        self.calls.append(("get_record_stream", list(keys), sort_order,
 
591
            include_delta_closure))
 
592
        return self._backing_vf.get_record_stream(keys, sort_order,
 
593
            include_delta_closure)
 
594
 
 
595
    def get_sha1s(self, keys):
 
596
        self.calls.append(("get_sha1s", copy(keys)))
 
597
        return self._backing_vf.get_sha1s(keys)
 
598
 
 
599
    def iter_lines_added_or_present_in_keys(self, keys, pb=None):
 
600
        self.calls.append(("iter_lines_added_or_present_in_keys", copy(keys)))
 
601
        return self._backing_vf.iter_lines_added_or_present_in_keys(keys, pb=pb)
 
602
 
 
603
    def keys(self):
 
604
        self.calls.append(("keys",))
 
605
        return self._backing_vf.keys()
 
606
 
 
607
 
 
608
class OrderingVersionedFilesDecorator(RecordingVersionedFilesDecorator):
 
609
    """A VF that records calls, and returns keys in specific order.
 
610
 
 
611
    :ivar calls: A list of the calls made; can be reset at any time by
 
612
        assigning [] to it.
 
613
    """
 
614
 
 
615
    def __init__(self, backing_vf, key_priority):
 
616
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
 
617
 
 
618
        :param backing_vf: The versioned file to answer all methods.
 
619
        :param key_priority: A dictionary defining what order keys should be
 
620
            returned from an 'unordered' get_record_stream request.
 
621
            Keys with lower priority are returned first, keys not present in
 
622
            the map get an implicit priority of 0, and are returned in
 
623
            lexicographical order.
 
624
        """
 
625
        RecordingVersionedFilesDecorator.__init__(self, backing_vf)
 
626
        self._key_priority = key_priority
 
627
 
 
628
    def get_record_stream(self, keys, sort_order, include_delta_closure):
 
629
        self.calls.append(("get_record_stream", list(keys), sort_order,
 
630
            include_delta_closure))
 
631
        if sort_order == 'unordered':
 
632
            def sort_key(key):
 
633
                return (self._key_priority.get(key, 0), key)
 
634
            # Use a defined order by asking for the keys one-by-one from the
 
635
            # backing_vf
 
636
            for key in sorted(keys, key=sort_key):
 
637
                for record in self._backing_vf.get_record_stream([key],
 
638
                                'unordered', include_delta_closure):
 
639
                    yield record
 
640
        else:
 
641
            for record in self._backing_vf.get_record_stream(keys, sort_order,
 
642
                            include_delta_closure):
 
643
                yield record
 
644
 
 
645
 
 
646
class KeyMapper(object):
 
647
    """KeyMappers map between keys and underlying partitioned storage."""
 
648
 
 
649
    def map(self, key):
 
650
        """Map key to an underlying storage identifier.
 
651
 
 
652
        :param key: A key tuple e.g. ('file-id', 'revision-id').
 
653
        :return: An underlying storage identifier, specific to the partitioning
 
654
            mechanism.
 
655
        """
 
656
        raise NotImplementedError(self.map)
 
657
 
 
658
    def unmap(self, partition_id):
 
659
        """Map a partitioned storage id back to a key prefix.
 
660
 
 
661
        :param partition_id: The underlying partition id.
 
662
        :return: As much of a key (or prefix) as is derivable from the partition
 
663
            id.
 
664
        """
 
665
        raise NotImplementedError(self.unmap)
 
666
 
 
667
 
 
668
class ConstantMapper(KeyMapper):
 
669
    """A key mapper that maps to a constant result."""
 
670
 
 
671
    def __init__(self, result):
 
672
        """Create a ConstantMapper which will return result for all maps."""
 
673
        self._result = result
 
674
 
 
675
    def map(self, key):
 
676
        """See KeyMapper.map()."""
 
677
        return self._result
 
678
 
 
679
 
 
680
class URLEscapeMapper(KeyMapper):
 
681
    """Base class for use with transport backed storage.
 
682
 
 
683
    This provides a map and unmap wrapper that respectively url escape and
 
684
    unescape their outputs and inputs.
 
685
    """
 
686
 
 
687
    def map(self, key):
 
688
        """See KeyMapper.map()."""
 
689
        return urllib.quote(self._map(key))
 
690
 
 
691
    def unmap(self, partition_id):
 
692
        """See KeyMapper.unmap()."""
 
693
        return self._unmap(urllib.unquote(partition_id))
 
694
 
 
695
 
 
696
class PrefixMapper(URLEscapeMapper):
 
697
    """A key mapper that extracts the first component of a key.
 
698
 
 
699
    This mapper is for use with a transport based backend.
 
700
    """
 
701
 
 
702
    def _map(self, key):
 
703
        """See KeyMapper.map()."""
 
704
        return key[0]
 
705
 
 
706
    def _unmap(self, partition_id):
 
707
        """See KeyMapper.unmap()."""
 
708
        return (partition_id,)
 
709
 
 
710
 
 
711
class HashPrefixMapper(URLEscapeMapper):
 
712
    """A key mapper that combines the first component of a key with a hash.
 
713
 
 
714
    This mapper is for use with a transport based backend.
 
715
    """
 
716
 
 
717
    def _map(self, key):
 
718
        """See KeyMapper.map()."""
 
719
        prefix = self._escape(key[0])
 
720
        return "%02x/%s" % (adler32(prefix) & 0xff, prefix)
 
721
 
 
722
    def _escape(self, prefix):
 
723
        """No escaping needed here."""
 
724
        return prefix
 
725
 
 
726
    def _unmap(self, partition_id):
 
727
        """See KeyMapper.unmap()."""
 
728
        return (self._unescape(osutils.basename(partition_id)),)
 
729
 
 
730
    def _unescape(self, basename):
 
731
        """No unescaping needed for HashPrefixMapper."""
 
732
        return basename
 
733
 
 
734
 
 
735
class HashEscapedPrefixMapper(HashPrefixMapper):
 
736
    """Combines the escaped first component of a key with a hash.
 
737
 
 
738
    This mapper is for use with a transport based backend.
 
739
    """
 
740
 
 
741
    _safe = "abcdefghijklmnopqrstuvwxyz0123456789-_@,."
 
742
 
 
743
    def _escape(self, prefix):
 
744
        """Turn a key element into a filesystem safe string.
 
745
 
 
746
        This is similar to a plain urllib.quote, except
 
747
        it uses specific safe characters, so that it doesn't
 
748
        have to translate a lot of valid file ids.
 
749
        """
 
750
        # @ does not get escaped. This is because it is a valid
 
751
        # filesystem character we use all the time, and it looks
 
752
        # a lot better than seeing %40 all the time.
 
753
        r = [((c in self._safe) and c or ('%%%02x' % ord(c)))
 
754
             for c in prefix]
 
755
        return ''.join(r)
 
756
 
 
757
    def _unescape(self, basename):
 
758
        """Escaped names are easily unescaped by urlutils."""
 
759
        return urllib.unquote(basename)
 
760
 
 
761
 
 
762
def make_versioned_files_factory(versioned_file_factory, mapper):
 
763
    """Create a ThunkedVersionedFiles factory.
 
764
 
 
765
    This will create a callable which when called creates a
 
766
    ThunkedVersionedFiles on a transport, using mapper to access individual
 
767
    versioned files, and versioned_file_factory to create each individual file.
 
768
    """
 
769
    def factory(transport):
 
770
        return ThunkedVersionedFiles(transport, versioned_file_factory, mapper,
 
771
            lambda:True)
 
772
    return factory
 
773
 
 
774
 
 
775
class VersionedFiles(object):
 
776
    """Storage for many versioned files.
 
777
 
 
778
    This object allows a single keyspace for accessing the history graph and
 
779
    contents of named bytestrings.
 
780
 
 
781
    Currently no implementation allows the graph of different key prefixes to
 
782
    intersect, but the API does allow such implementations in the future.
 
783
 
 
784
    The keyspace is expressed via simple tuples. Any instance of VersionedFiles
 
785
    may have a different length key-size, but that size will be constant for
 
786
    all texts added to or retrieved from it. For instance, bzrlib uses
 
787
    instances with a key-size of 2 for storing user files in a repository, with
 
788
    the first element the fileid, and the second the version of that file.
 
789
 
 
790
    The use of tuples allows a single code base to support several different
 
791
    uses with only the mapping logic changing from instance to instance.
 
792
    """
 
793
 
 
794
    def add_lines(self, key, parents, lines, parent_texts=None,
 
795
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
796
        check_content=True):
 
797
        """Add a text to the store.
 
798
 
 
799
        :param key: The key tuple of the text to add. If the last element is
 
800
            None, a CHK string will be generated during the addition.
 
801
        :param parents: The parents key tuples of the text to add.
 
802
        :param lines: A list of lines. Each line must be a bytestring. And all
 
803
            of them except the last must be terminated with \n and contain no
 
804
            other \n's. The last line may either contain no \n's or a single
 
805
            terminating \n. If the lines list does meet this constraint the add
 
806
            routine may error or may succeed - but you will be unable to read
 
807
            the data back accurately. (Checking the lines have been split
 
808
            correctly is expensive and extremely unlikely to catch bugs so it
 
809
            is not done at runtime unless check_content is True.)
 
810
        :param parent_texts: An optional dictionary containing the opaque
 
811
            representations of some or all of the parents of version_id to
 
812
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
 
813
            returned by add_lines or data corruption can be caused.
 
814
        :param left_matching_blocks: a hint about which areas are common
 
815
            between the text and its left-hand-parent.  The format is
 
816
            the SequenceMatcher.get_matching_blocks format.
 
817
        :param nostore_sha: Raise ExistingContent and do not add the lines to
 
818
            the versioned file if the digest of the lines matches this.
 
819
        :param random_id: If True a random id has been selected rather than
 
820
            an id determined by some deterministic process such as a converter
 
821
            from a foreign VCS. When True the backend may choose not to check
 
822
            for uniqueness of the resulting key within the versioned file, so
 
823
            this should only be done when the result is expected to be unique
 
824
            anyway.
 
825
        :param check_content: If True, the lines supplied are verified to be
 
826
            bytestrings that are correctly formed lines.
 
827
        :return: The text sha1, the number of bytes in the text, and an opaque
 
828
                 representation of the inserted version which can be provided
 
829
                 back to future add_lines calls in the parent_texts dictionary.
 
830
        """
 
831
        raise NotImplementedError(self.add_lines)
 
832
 
 
833
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
 
834
        """Add a text to the store.
 
835
 
 
836
        This is a private function for use by CommitBuilder.
 
837
 
 
838
        :param key: The key tuple of the text to add. If the last element is
 
839
            None, a CHK string will be generated during the addition.
 
840
        :param parents: The parents key tuples of the text to add.
 
841
        :param text: A string containing the text to be committed.
 
842
        :param nostore_sha: Raise ExistingContent and do not add the lines to
 
843
            the versioned file if the digest of the lines matches this.
 
844
        :param random_id: If True a random id has been selected rather than
 
845
            an id determined by some deterministic process such as a converter
 
846
            from a foreign VCS. When True the backend may choose not to check
 
847
            for uniqueness of the resulting key within the versioned file, so
 
848
            this should only be done when the result is expected to be unique
 
849
            anyway.
 
850
        :param check_content: If True, the lines supplied are verified to be
 
851
            bytestrings that are correctly formed lines.
 
852
        :return: The text sha1, the number of bytes in the text, and an opaque
 
853
                 representation of the inserted version which can be provided
 
854
                 back to future _add_text calls in the parent_texts dictionary.
 
855
        """
 
856
        # The default implementation just thunks over to .add_lines(),
 
857
        # inefficient, but it works.
 
858
        return self.add_lines(key, parents, osutils.split_lines(text),
 
859
                              nostore_sha=nostore_sha,
 
860
                              random_id=random_id,
 
861
                              check_content=True)
 
862
 
 
863
    def add_mpdiffs(self, records):
 
864
        """Add mpdiffs to this VersionedFile.
 
865
 
 
866
        Records should be iterables of version, parents, expected_sha1,
 
867
        mpdiff. mpdiff should be a MultiParent instance.
 
868
        """
 
869
        vf_parents = {}
 
870
        mpvf = multiparent.MultiMemoryVersionedFile()
 
871
        versions = []
 
872
        for version, parent_ids, expected_sha1, mpdiff in records:
 
873
            versions.append(version)
 
874
            mpvf.add_diff(mpdiff, version, parent_ids)
 
875
        needed_parents = set()
 
876
        for version, parent_ids, expected_sha1, mpdiff in records:
 
877
            needed_parents.update(p for p in parent_ids
 
878
                                  if not mpvf.has_version(p))
 
879
        # It seems likely that adding all the present parents as fulltexts can
 
880
        # easily exhaust memory.
 
881
        chunks_to_lines = osutils.chunks_to_lines
 
882
        for record in self.get_record_stream(needed_parents, 'unordered',
 
883
            True):
 
884
            if record.storage_kind == 'absent':
 
885
                continue
 
886
            mpvf.add_version(chunks_to_lines(record.get_bytes_as('chunked')),
 
887
                record.key, [])
 
888
        for (key, parent_keys, expected_sha1, mpdiff), lines in\
 
889
            zip(records, mpvf.get_line_list(versions)):
 
890
            if len(parent_keys) == 1:
 
891
                left_matching_blocks = list(mpdiff.get_matching_blocks(0,
 
892
                    mpvf.get_diff(parent_keys[0]).num_lines()))
 
893
            else:
 
894
                left_matching_blocks = None
 
895
            version_sha1, _, version_text = self.add_lines(key,
 
896
                parent_keys, lines, vf_parents,
 
897
                left_matching_blocks=left_matching_blocks)
 
898
            if version_sha1 != expected_sha1:
 
899
                raise errors.VersionedFileInvalidChecksum(version)
 
900
            vf_parents[key] = version_text
 
901
 
 
902
    def annotate(self, key):
 
903
        """Return a list of (version-key, line) tuples for the text of key.
 
904
 
 
905
        :raise RevisionNotPresent: If the key is not present.
 
906
        """
 
907
        raise NotImplementedError(self.annotate)
 
908
 
 
909
    def check(self, progress_bar=None):
 
910
        """Check this object for integrity."""
 
911
        raise NotImplementedError(self.check)
 
912
 
 
913
    @staticmethod
 
914
    def check_not_reserved_id(version_id):
 
915
        revision.check_not_reserved_id(version_id)
 
916
 
 
917
    def _check_lines_not_unicode(self, lines):
 
918
        """Check that lines being added to a versioned file are not unicode."""
 
919
        for line in lines:
 
920
            if line.__class__ is not str:
 
921
                raise errors.BzrBadParameterUnicode("lines")
 
922
 
 
923
    def _check_lines_are_lines(self, lines):
 
924
        """Check that the lines really are full lines without inline EOL."""
 
925
        for line in lines:
 
926
            if '\n' in line[:-1]:
 
927
                raise errors.BzrBadParameterContainsNewline("lines")
 
928
 
 
929
    def get_parent_map(self, keys):
 
930
        """Get a map of the parents of keys.
 
931
 
 
932
        :param keys: The keys to look up parents for.
 
933
        :return: A mapping from keys to parents. Absent keys are absent from
 
934
            the mapping.
 
935
        """
 
936
        raise NotImplementedError(self.get_parent_map)
 
937
 
 
938
    def get_record_stream(self, keys, ordering, include_delta_closure):
 
939
        """Get a stream of records for keys.
 
940
 
 
941
        :param keys: The keys to include.
 
942
        :param ordering: Either 'unordered' or 'topological'. A topologically
 
943
            sorted stream has compression parents strictly before their
 
944
            children.
 
945
        :param include_delta_closure: If True then the closure across any
 
946
            compression parents will be included (in the opaque data).
 
947
        :return: An iterator of ContentFactory objects, each of which is only
 
948
            valid until the iterator is advanced.
 
949
        """
 
950
        raise NotImplementedError(self.get_record_stream)
 
951
 
 
952
    def get_sha1s(self, keys):
 
953
        """Get the sha1's of the texts for the given keys.
 
954
 
 
955
        :param keys: The names of the keys to lookup
 
956
        :return: a dict from key to sha1 digest. Keys of texts which are not
 
957
            present in the store are not present in the returned
 
958
            dictionary.
 
959
        """
 
960
        raise NotImplementedError(self.get_sha1s)
 
961
 
 
962
    has_key = index._has_key_from_parent_map
 
963
 
 
964
    def get_missing_compression_parent_keys(self):
 
965
        """Return an iterable of keys of missing compression parents.
 
966
 
 
967
        Check this after calling insert_record_stream to find out if there are
 
968
        any missing compression parents.  If there are, the records that
 
969
        depend on them are not able to be inserted safely. The precise
 
970
        behaviour depends on the concrete VersionedFiles class in use.
 
971
 
 
972
        Classes that do not support this will raise NotImplementedError.
 
973
        """
 
974
        raise NotImplementedError(self.get_missing_compression_parent_keys)
 
975
 
 
976
    def insert_record_stream(self, stream):
 
977
        """Insert a record stream into this container.
 
978
 
 
979
        :param stream: A stream of records to insert.
 
980
        :return: None
 
981
        :seealso VersionedFile.get_record_stream:
 
982
        """
 
983
        raise NotImplementedError
 
984
 
 
985
    def iter_lines_added_or_present_in_keys(self, keys, pb=None):
 
986
        """Iterate over the lines in the versioned files from keys.
 
987
 
 
988
        This may return lines from other keys. Each item the returned
 
989
        iterator yields is a tuple of a line and a text version that that line
 
990
        is present in (not introduced in).
 
991
 
 
992
        Ordering of results is in whatever order is most suitable for the
 
993
        underlying storage format.
 
994
 
 
995
        If a progress bar is supplied, it may be used to indicate progress.
 
996
        The caller is responsible for cleaning up progress bars (because this
 
997
        is an iterator).
 
998
 
 
999
        NOTES:
 
1000
         * Lines are normalised by the underlying store: they will all have \n
 
1001
           terminators.
 
1002
         * Lines are returned in arbitrary order.
 
1003
 
 
1004
        :return: An iterator over (line, key).
 
1005
        """
 
1006
        raise NotImplementedError(self.iter_lines_added_or_present_in_keys)
 
1007
 
 
1008
    def keys(self):
 
1009
        """Return a iterable of the keys for all the contained texts."""
 
1010
        raise NotImplementedError(self.keys)
 
1011
 
 
1012
    def make_mpdiffs(self, keys):
 
1013
        """Create multiparent diffs for specified keys."""
 
1014
        keys_order = tuple(keys)
 
1015
        keys = frozenset(keys)
 
1016
        knit_keys = set(keys)
 
1017
        parent_map = self.get_parent_map(keys)
 
1018
        for parent_keys in parent_map.itervalues():
 
1019
            if parent_keys:
 
1020
                knit_keys.update(parent_keys)
 
1021
        missing_keys = keys - set(parent_map)
 
1022
        if missing_keys:
 
1023
            raise errors.RevisionNotPresent(list(missing_keys)[0], self)
 
1024
        # We need to filter out ghosts, because we can't diff against them.
 
1025
        maybe_ghosts = knit_keys - keys
 
1026
        ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
 
1027
        knit_keys.difference_update(ghosts)
 
1028
        lines = {}
 
1029
        chunks_to_lines = osutils.chunks_to_lines
 
1030
        for record in self.get_record_stream(knit_keys, 'topological', True):
 
1031
            lines[record.key] = chunks_to_lines(record.get_bytes_as('chunked'))
 
1032
            # line_block_dict = {}
 
1033
            # for parent, blocks in record.extract_line_blocks():
 
1034
            #   line_blocks[parent] = blocks
 
1035
            # line_blocks[record.key] = line_block_dict
 
1036
        diffs = []
 
1037
        for key in keys_order:
 
1038
            target = lines[key]
 
1039
            parents = parent_map[key] or []
 
1040
            # Note that filtering knit_keys can lead to a parent difference
 
1041
            # between the creation and the application of the mpdiff.
 
1042
            parent_lines = [lines[p] for p in parents if p in knit_keys]
 
1043
            if len(parent_lines) > 0:
 
1044
                left_parent_blocks = self._extract_blocks(key, parent_lines[0],
 
1045
                    target)
 
1046
            else:
 
1047
                left_parent_blocks = None
 
1048
            diffs.append(multiparent.MultiParent.from_lines(target,
 
1049
                parent_lines, left_parent_blocks))
 
1050
        return diffs
 
1051
 
 
1052
    missing_keys = index._missing_keys_from_parent_map
 
1053
 
 
1054
    def _extract_blocks(self, version_id, source, target):
 
1055
        return None
 
1056
 
 
1057
 
 
1058
class ThunkedVersionedFiles(VersionedFiles):
 
1059
    """Storage for many versioned files thunked onto a 'VersionedFile' class.
 
1060
 
 
1061
    This object allows a single keyspace for accessing the history graph and
 
1062
    contents of named bytestrings.
 
1063
 
 
1064
    Currently no implementation allows the graph of different key prefixes to
 
1065
    intersect, but the API does allow such implementations in the future.
 
1066
    """
 
1067
 
 
1068
    def __init__(self, transport, file_factory, mapper, is_locked):
 
1069
        """Create a ThunkedVersionedFiles."""
 
1070
        self._transport = transport
 
1071
        self._file_factory = file_factory
 
1072
        self._mapper = mapper
 
1073
        self._is_locked = is_locked
 
1074
 
 
1075
    def add_lines(self, key, parents, lines, parent_texts=None,
 
1076
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
1077
        check_content=True):
 
1078
        """See VersionedFiles.add_lines()."""
 
1079
        path = self._mapper.map(key)
 
1080
        version_id = key[-1]
 
1081
        parents = [parent[-1] for parent in parents]
 
1082
        vf = self._get_vf(path)
 
1083
        try:
 
1084
            try:
 
1085
                return vf.add_lines_with_ghosts(version_id, parents, lines,
 
1086
                    parent_texts=parent_texts,
 
1087
                    left_matching_blocks=left_matching_blocks,
 
1088
                    nostore_sha=nostore_sha, random_id=random_id,
 
1089
                    check_content=check_content)
 
1090
            except NotImplementedError:
 
1091
                return vf.add_lines(version_id, parents, lines,
 
1092
                    parent_texts=parent_texts,
 
1093
                    left_matching_blocks=left_matching_blocks,
 
1094
                    nostore_sha=nostore_sha, random_id=random_id,
 
1095
                    check_content=check_content)
 
1096
        except errors.NoSuchFile:
 
1097
            # parent directory may be missing, try again.
 
1098
            self._transport.mkdir(osutils.dirname(path))
 
1099
            try:
 
1100
                return vf.add_lines_with_ghosts(version_id, parents, lines,
 
1101
                    parent_texts=parent_texts,
 
1102
                    left_matching_blocks=left_matching_blocks,
 
1103
                    nostore_sha=nostore_sha, random_id=random_id,
 
1104
                    check_content=check_content)
 
1105
            except NotImplementedError:
 
1106
                return vf.add_lines(version_id, parents, lines,
 
1107
                    parent_texts=parent_texts,
 
1108
                    left_matching_blocks=left_matching_blocks,
 
1109
                    nostore_sha=nostore_sha, random_id=random_id,
 
1110
                    check_content=check_content)
 
1111
 
 
1112
    def annotate(self, key):
 
1113
        """Return a list of (version-key, line) tuples for the text of key.
 
1114
 
 
1115
        :raise RevisionNotPresent: If the key is not present.
 
1116
        """
 
1117
        prefix = key[:-1]
 
1118
        path = self._mapper.map(prefix)
 
1119
        vf = self._get_vf(path)
 
1120
        origins = vf.annotate(key[-1])
 
1121
        result = []
 
1122
        for origin, line in origins:
 
1123
            result.append((prefix + (origin,), line))
 
1124
        return result
 
1125
 
 
1126
    def get_annotator(self):
 
1127
        return annotate.Annotator(self)
 
1128
 
 
1129
    def check(self, progress_bar=None):
 
1130
        """See VersionedFiles.check()."""
 
1131
        for prefix, vf in self._iter_all_components():
 
1132
            vf.check()
 
1133
 
 
1134
    def get_parent_map(self, keys):
 
1135
        """Get a map of the parents of keys.
 
1136
 
 
1137
        :param keys: The keys to look up parents for.
 
1138
        :return: A mapping from keys to parents. Absent keys are absent from
 
1139
            the mapping.
 
1140
        """
 
1141
        prefixes = self._partition_keys(keys)
 
1142
        result = {}
 
1143
        for prefix, suffixes in prefixes.items():
 
1144
            path = self._mapper.map(prefix)
 
1145
            vf = self._get_vf(path)
 
1146
            parent_map = vf.get_parent_map(suffixes)
 
1147
            for key, parents in parent_map.items():
 
1148
                result[prefix + (key,)] = tuple(
 
1149
                    prefix + (parent,) for parent in parents)
 
1150
        return result
 
1151
 
 
1152
    def _get_vf(self, path):
 
1153
        if not self._is_locked():
 
1154
            raise errors.ObjectNotLocked(self)
 
1155
        return self._file_factory(path, self._transport, create=True,
 
1156
            get_scope=lambda:None)
 
1157
 
 
1158
    def _partition_keys(self, keys):
 
1159
        """Turn keys into a dict of prefix:suffix_list."""
 
1160
        result = {}
 
1161
        for key in keys:
 
1162
            prefix_keys = result.setdefault(key[:-1], [])
 
1163
            prefix_keys.append(key[-1])
 
1164
        return result
 
1165
 
 
1166
    def _get_all_prefixes(self):
 
1167
        # Identify all key prefixes.
 
1168
        # XXX: A bit hacky, needs polish.
 
1169
        if type(self._mapper) == ConstantMapper:
 
1170
            paths = [self._mapper.map(())]
 
1171
            prefixes = [()]
 
1172
        else:
 
1173
            relpaths = set()
 
1174
            for quoted_relpath in self._transport.iter_files_recursive():
 
1175
                path, ext = os.path.splitext(quoted_relpath)
 
1176
                relpaths.add(path)
 
1177
            paths = list(relpaths)
 
1178
            prefixes = [self._mapper.unmap(path) for path in paths]
 
1179
        return zip(paths, prefixes)
 
1180
 
 
1181
    def get_record_stream(self, keys, ordering, include_delta_closure):
 
1182
        """See VersionedFiles.get_record_stream()."""
 
1183
        # Ordering will be taken care of by each partitioned store; group keys
 
1184
        # by partition.
 
1185
        keys = sorted(keys)
 
1186
        for prefix, suffixes, vf in self._iter_keys_vf(keys):
 
1187
            suffixes = [(suffix,) for suffix in suffixes]
 
1188
            for record in vf.get_record_stream(suffixes, ordering,
 
1189
                include_delta_closure):
 
1190
                if record.parents is not None:
 
1191
                    record.parents = tuple(
 
1192
                        prefix + parent for parent in record.parents)
 
1193
                record.key = prefix + record.key
 
1194
                yield record
 
1195
 
 
1196
    def _iter_keys_vf(self, keys):
 
1197
        prefixes = self._partition_keys(keys)
 
1198
        sha1s = {}
 
1199
        for prefix, suffixes in prefixes.items():
 
1200
            path = self._mapper.map(prefix)
 
1201
            vf = self._get_vf(path)
 
1202
            yield prefix, suffixes, vf
 
1203
 
 
1204
    def get_sha1s(self, keys):
 
1205
        """See VersionedFiles.get_sha1s()."""
 
1206
        sha1s = {}
 
1207
        for prefix,suffixes, vf in self._iter_keys_vf(keys):
 
1208
            vf_sha1s = vf.get_sha1s(suffixes)
 
1209
            for suffix, sha1 in vf_sha1s.iteritems():
 
1210
                sha1s[prefix + (suffix,)] = sha1
 
1211
        return sha1s
 
1212
 
 
1213
    def insert_record_stream(self, stream):
 
1214
        """Insert a record stream into this container.
 
1215
 
 
1216
        :param stream: A stream of records to insert.
 
1217
        :return: None
 
1218
        :seealso VersionedFile.get_record_stream:
 
1219
        """
 
1220
        for record in stream:
 
1221
            prefix = record.key[:-1]
 
1222
            key = record.key[-1:]
 
1223
            if record.parents is not None:
 
1224
                parents = [parent[-1:] for parent in record.parents]
 
1225
            else:
 
1226
                parents = None
 
1227
            thunk_record = AdapterFactory(key, parents, record)
 
1228
            path = self._mapper.map(prefix)
 
1229
            # Note that this parses the file many times; we can do better but
 
1230
            # as this only impacts weaves in terms of performance, it is
 
1231
            # tolerable.
 
1232
            vf = self._get_vf(path)
 
1233
            vf.insert_record_stream([thunk_record])
 
1234
 
 
1235
    def iter_lines_added_or_present_in_keys(self, keys, pb=None):
 
1236
        """Iterate over the lines in the versioned files from keys.
 
1237
 
 
1238
        This may return lines from other keys. Each item the returned
 
1239
        iterator yields is a tuple of a line and a text version that that line
 
1240
        is present in (not introduced in).
 
1241
 
 
1242
        Ordering of results is in whatever order is most suitable for the
 
1243
        underlying storage format.
 
1244
 
 
1245
        If a progress bar is supplied, it may be used to indicate progress.
 
1246
        The caller is responsible for cleaning up progress bars (because this
 
1247
        is an iterator).
 
1248
 
 
1249
        NOTES:
 
1250
         * Lines are normalised by the underlying store: they will all have \n
 
1251
           terminators.
 
1252
         * Lines are returned in arbitrary order.
 
1253
 
 
1254
        :return: An iterator over (line, key).
 
1255
        """
 
1256
        for prefix, suffixes, vf in self._iter_keys_vf(keys):
 
1257
            for line, version in vf.iter_lines_added_or_present_in_versions(suffixes):
 
1258
                yield line, prefix + (version,)
 
1259
 
 
1260
    def _iter_all_components(self):
 
1261
        for path, prefix in self._get_all_prefixes():
 
1262
            yield prefix, self._get_vf(path)
 
1263
 
 
1264
    def keys(self):
 
1265
        """See VersionedFiles.keys()."""
 
1266
        result = set()
 
1267
        for prefix, vf in self._iter_all_components():
 
1268
            for suffix in vf.versions():
 
1269
                result.add(prefix + (suffix,))
 
1270
        return result
 
1271
 
 
1272
 
 
1273
class _PlanMergeVersionedFile(VersionedFiles):
 
1274
    """A VersionedFile for uncommitted and committed texts.
 
1275
 
 
1276
    It is intended to allow merges to be planned with working tree texts.
 
1277
    It implements only the small part of the VersionedFiles interface used by
 
1278
    PlanMerge.  It falls back to multiple versionedfiles for data not stored in
 
1279
    _PlanMergeVersionedFile itself.
 
1280
 
 
1281
    :ivar: fallback_versionedfiles a list of VersionedFiles objects that can be
 
1282
        queried for missing texts.
 
1283
    """
 
1284
 
 
1285
    def __init__(self, file_id):
 
1286
        """Create a _PlanMergeVersionedFile.
 
1287
 
 
1288
        :param file_id: Used with _PlanMerge code which is not yet fully
 
1289
            tuple-keyspace aware.
 
1290
        """
 
1291
        self._file_id = file_id
 
1292
        # fallback locations
 
1293
        self.fallback_versionedfiles = []
 
1294
        # Parents for locally held keys.
 
1295
        self._parents = {}
 
1296
        # line data for locally held keys.
 
1297
        self._lines = {}
 
1298
        # key lookup providers
 
1299
        self._providers = [DictParentsProvider(self._parents)]
 
1300
 
 
1301
    def plan_merge(self, ver_a, ver_b, base=None):
 
1302
        """See VersionedFile.plan_merge"""
 
1303
        from bzrlib.merge import _PlanMerge
 
1304
        if base is None:
 
1305
            return _PlanMerge(ver_a, ver_b, self, (self._file_id,)).plan_merge()
 
1306
        old_plan = list(_PlanMerge(ver_a, base, self, (self._file_id,)).plan_merge())
 
1307
        new_plan = list(_PlanMerge(ver_a, ver_b, self, (self._file_id,)).plan_merge())
 
1308
        return _PlanMerge._subtract_plans(old_plan, new_plan)
 
1309
 
 
1310
    def plan_lca_merge(self, ver_a, ver_b, base=None):
 
1311
        from bzrlib.merge import _PlanLCAMerge
 
1312
        graph = Graph(self)
 
1313
        new_plan = _PlanLCAMerge(ver_a, ver_b, self, (self._file_id,), graph).plan_merge()
 
1314
        if base is None:
 
1315
            return new_plan
 
1316
        old_plan = _PlanLCAMerge(ver_a, base, self, (self._file_id,), graph).plan_merge()
 
1317
        return _PlanLCAMerge._subtract_plans(list(old_plan), list(new_plan))
 
1318
 
 
1319
    def add_lines(self, key, parents, lines):
 
1320
        """See VersionedFiles.add_lines
 
1321
 
 
1322
        Lines are added locally, not to fallback versionedfiles.  Also, ghosts
 
1323
        are permitted.  Only reserved ids are permitted.
 
1324
        """
 
1325
        if type(key) is not tuple:
 
1326
            raise TypeError(key)
 
1327
        if not revision.is_reserved_id(key[-1]):
 
1328
            raise ValueError('Only reserved ids may be used')
 
1329
        if parents is None:
 
1330
            raise ValueError('Parents may not be None')
 
1331
        if lines is None:
 
1332
            raise ValueError('Lines may not be None')
 
1333
        self._parents[key] = tuple(parents)
 
1334
        self._lines[key] = lines
 
1335
 
 
1336
    def get_record_stream(self, keys, ordering, include_delta_closure):
 
1337
        pending = set(keys)
 
1338
        for key in keys:
 
1339
            if key in self._lines:
 
1340
                lines = self._lines[key]
 
1341
                parents = self._parents[key]
 
1342
                pending.remove(key)
 
1343
                yield ChunkedContentFactory(key, parents, None, lines)
 
1344
        for versionedfile in self.fallback_versionedfiles:
 
1345
            for record in versionedfile.get_record_stream(
 
1346
                pending, 'unordered', True):
 
1347
                if record.storage_kind == 'absent':
 
1348
                    continue
 
1349
                else:
 
1350
                    pending.remove(record.key)
 
1351
                    yield record
 
1352
            if not pending:
 
1353
                return
 
1354
        # report absent entries
 
1355
        for key in pending:
 
1356
            yield AbsentContentFactory(key)
 
1357
 
 
1358
    def get_parent_map(self, keys):
 
1359
        """See VersionedFiles.get_parent_map"""
 
1360
        # We create a new provider because a fallback may have been added.
 
1361
        # If we make fallbacks private we can update a stack list and avoid
 
1362
        # object creation thrashing.
 
1363
        keys = set(keys)
 
1364
        result = {}
 
1365
        if revision.NULL_REVISION in keys:
 
1366
            keys.remove(revision.NULL_REVISION)
 
1367
            result[revision.NULL_REVISION] = ()
 
1368
        self._providers = self._providers[:1] + self.fallback_versionedfiles
 
1369
        result.update(
 
1370
            StackedParentsProvider(self._providers).get_parent_map(keys))
 
1371
        for key, parents in result.iteritems():
 
1372
            if parents == ():
 
1373
                result[key] = (revision.NULL_REVISION,)
 
1374
        return result
 
1375
 
 
1376
 
502
1377
class PlanWeaveMerge(TextMerge):
503
1378
    """Weave merge that takes a plan as its input.
504
 
    
 
1379
 
505
1380
    This exists so that VersionedFile.plan_merge is implementable.
506
1381
    Most callers will want to use WeaveMerge instead.
507
1382
    """
528
1403
                yield(lines_a,)
529
1404
            else:
530
1405
                yield (lines_a, lines_b)
531
 
       
 
1406
 
532
1407
        # We previously considered either 'unchanged' or 'killed-both' lines
533
1408
        # to be possible places to resynchronize.  However, assuming agreement
534
1409
        # on killed-both lines may be too aggressive. -- mbp 20060324
540
1415
                lines_a = []
541
1416
                lines_b = []
542
1417
                ch_a = ch_b = False
543
 
                
 
1418
 
544
1419
            if state == 'unchanged':
545
1420
                if line:
546
1421
                    yield ([line],)
556
1431
            elif state == 'new-b':
557
1432
                ch_b = True
558
1433
                lines_b.append(line)
 
1434
            elif state == 'conflicted-a':
 
1435
                ch_b = ch_a = True
 
1436
                lines_a.append(line)
 
1437
            elif state == 'conflicted-b':
 
1438
                ch_b = ch_a = True
 
1439
                lines_b.append(line)
 
1440
            elif state == 'killed-both':
 
1441
                # This counts as a change, even though there is no associated
 
1442
                # line
 
1443
                ch_b = ch_a = True
559
1444
            else:
560
 
                assert state in ('irrelevant', 'ghost-a', 'ghost-b', 
561
 
                                 'killed-base', 'killed-both'), state
 
1445
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
 
1446
                        'killed-base'):
 
1447
                    raise AssertionError(state)
562
1448
        for struct in outstanding_struct():
563
1449
            yield struct
564
1450
 
565
1451
 
566
1452
class WeaveMerge(PlanWeaveMerge):
567
 
    """Weave merge that takes a VersionedFile and two versions as its input"""
 
1453
    """Weave merge that takes a VersionedFile and two versions as its input."""
568
1454
 
569
 
    def __init__(self, versionedfile, ver_a, ver_b, 
 
1455
    def __init__(self, versionedfile, ver_a, ver_b,
570
1456
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
571
1457
        plan = versionedfile.plan_merge(ver_a, ver_b)
572
1458
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
573
1459
 
574
1460
 
575
 
class InterVersionedFile(InterObject):
576
 
    """This class represents operations taking place between two versionedfiles..
577
 
 
578
 
    Its instances have methods like join, and contain
579
 
    references to the source and target versionedfiles these operations can be 
580
 
    carried out on.
581
 
 
582
 
    Often we will provide convenience methods on 'versionedfile' which carry out
583
 
    operations with another versionedfile - they will always forward to
584
 
    InterVersionedFile.get(other).method_name(parameters).
585
 
    """
586
 
 
587
 
    _optimisers = []
588
 
    """The available optimised InterVersionedFile types."""
589
 
 
590
 
    def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
591
 
        """Integrate versions from self.source into self.target.
592
 
 
593
 
        If version_ids is None all versions from source should be
594
 
        incorporated into this versioned file.
595
 
 
596
 
        Must raise RevisionNotPresent if any of the specified versions
597
 
        are not present in the other files history unless ignore_missing is 
598
 
        supplied when they are silently skipped.
599
 
        """
600
 
        # the default join: 
601
 
        # - if the target is empty, just add all the versions from 
602
 
        #   source to target, otherwise:
603
 
        # - make a temporary versioned file of type target
604
 
        # - insert the source content into it one at a time
605
 
        # - join them
606
 
        if not self.target.versions():
607
 
            target = self.target
 
1461
class VirtualVersionedFiles(VersionedFiles):
 
1462
    """Dummy implementation for VersionedFiles that uses other functions for
 
1463
    obtaining fulltexts and parent maps.
 
1464
 
 
1465
    This is always on the bottom of the stack and uses string keys
 
1466
    (rather than tuples) internally.
 
1467
    """
 
1468
 
 
1469
    def __init__(self, get_parent_map, get_lines):
 
1470
        """Create a VirtualVersionedFiles.
 
1471
 
 
1472
        :param get_parent_map: Same signature as Repository.get_parent_map.
 
1473
        :param get_lines: Should return lines for specified key or None if
 
1474
                          not available.
 
1475
        """
 
1476
        super(VirtualVersionedFiles, self).__init__()
 
1477
        self._get_parent_map = get_parent_map
 
1478
        self._get_lines = get_lines
 
1479
 
 
1480
    def check(self, progressbar=None):
 
1481
        """See VersionedFiles.check.
 
1482
 
 
1483
        :note: Always returns True for VirtualVersionedFiles.
 
1484
        """
 
1485
        return True
 
1486
 
 
1487
    def add_mpdiffs(self, records):
 
1488
        """See VersionedFiles.mpdiffs.
 
1489
 
 
1490
        :note: Not implemented for VirtualVersionedFiles.
 
1491
        """
 
1492
        raise NotImplementedError(self.add_mpdiffs)
 
1493
 
 
1494
    def get_parent_map(self, keys):
 
1495
        """See VersionedFiles.get_parent_map."""
 
1496
        return dict([((k,), tuple([(p,) for p in v]))
 
1497
            for k,v in self._get_parent_map([k for (k,) in keys]).iteritems()])
 
1498
 
 
1499
    def get_sha1s(self, keys):
 
1500
        """See VersionedFiles.get_sha1s."""
 
1501
        ret = {}
 
1502
        for (k,) in keys:
 
1503
            lines = self._get_lines(k)
 
1504
            if lines is not None:
 
1505
                if not isinstance(lines, list):
 
1506
                    raise AssertionError
 
1507
                ret[(k,)] = osutils.sha_strings(lines)
 
1508
        return ret
 
1509
 
 
1510
    def get_record_stream(self, keys, ordering, include_delta_closure):
 
1511
        """See VersionedFiles.get_record_stream."""
 
1512
        for (k,) in list(keys):
 
1513
            lines = self._get_lines(k)
 
1514
            if lines is not None:
 
1515
                if not isinstance(lines, list):
 
1516
                    raise AssertionError
 
1517
                yield ChunkedContentFactory((k,), None,
 
1518
                        sha1=osutils.sha_strings(lines),
 
1519
                        chunks=lines)
 
1520
            else:
 
1521
                yield AbsentContentFactory((k,))
 
1522
 
 
1523
    def iter_lines_added_or_present_in_keys(self, keys, pb=None):
 
1524
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
 
1525
        for i, (key,) in enumerate(keys):
 
1526
            if pb is not None:
 
1527
                pb.update("Finding changed lines", i, len(keys))
 
1528
            for l in self._get_lines(key):
 
1529
                yield (l, key)
 
1530
 
 
1531
 
 
1532
def network_bytes_to_kind_and_offset(network_bytes):
 
1533
    """Strip of a record kind from the front of network_bytes.
 
1534
 
 
1535
    :param network_bytes: The bytes of a record.
 
1536
    :return: A tuple (storage_kind, offset_of_remaining_bytes)
 
1537
    """
 
1538
    line_end = network_bytes.find('\n')
 
1539
    storage_kind = network_bytes[:line_end]
 
1540
    return storage_kind, line_end + 1
 
1541
 
 
1542
 
 
1543
class NetworkRecordStream(object):
 
1544
    """A record_stream which reconstitures a serialised stream."""
 
1545
 
 
1546
    def __init__(self, bytes_iterator):
 
1547
        """Create a NetworkRecordStream.
 
1548
 
 
1549
        :param bytes_iterator: An iterator of bytes. Each item in this
 
1550
            iterator should have been obtained from a record_streams'
 
1551
            record.get_bytes_as(record.storage_kind) call.
 
1552
        """
 
1553
        self._bytes_iterator = bytes_iterator
 
1554
        self._kind_factory = {'knit-ft-gz':knit.knit_network_to_record,
 
1555
            'knit-delta-gz':knit.knit_network_to_record,
 
1556
            'knit-annotated-ft-gz':knit.knit_network_to_record,
 
1557
            'knit-annotated-delta-gz':knit.knit_network_to_record,
 
1558
            'knit-delta-closure':knit.knit_delta_closure_to_records,
 
1559
            'fulltext':fulltext_network_to_record,
 
1560
            'groupcompress-block':groupcompress.network_block_to_records,
 
1561
            }
 
1562
 
 
1563
    def read(self):
 
1564
        """Read the stream.
 
1565
 
 
1566
        :return: An iterator as per VersionedFiles.get_record_stream().
 
1567
        """
 
1568
        for bytes in self._bytes_iterator:
 
1569
            storage_kind, line_end = network_bytes_to_kind_and_offset(bytes)
 
1570
            for record in self._kind_factory[storage_kind](
 
1571
                storage_kind, bytes, line_end):
 
1572
                yield record
 
1573
 
 
1574
 
 
1575
def fulltext_network_to_record(kind, bytes, line_end):
 
1576
    """Convert a network fulltext record to record."""
 
1577
    meta_len, = struct.unpack('!L', bytes[line_end:line_end+4])
 
1578
    record_meta = bytes[line_end+4:line_end+4+meta_len]
 
1579
    key, parents = bencode.bdecode_as_tuple(record_meta)
 
1580
    if parents == 'nil':
 
1581
        parents = None
 
1582
    fulltext = bytes[line_end+4+meta_len:]
 
1583
    return [FulltextContentFactory(key, parents, None, fulltext)]
 
1584
 
 
1585
 
 
1586
def _length_prefix(bytes):
 
1587
    return struct.pack('!L', len(bytes))
 
1588
 
 
1589
 
 
1590
def record_to_fulltext_bytes(record):
 
1591
    if record.parents is None:
 
1592
        parents = 'nil'
 
1593
    else:
 
1594
        parents = record.parents
 
1595
    record_meta = bencode.bencode((record.key, parents))
 
1596
    record_content = record.get_bytes_as('fulltext')
 
1597
    return "fulltext\n%s%s%s" % (
 
1598
        _length_prefix(record_meta), record_meta, record_content)
 
1599
 
 
1600
 
 
1601
def sort_groupcompress(parent_map):
 
1602
    """Sort and group the keys in parent_map into groupcompress order.
 
1603
 
 
1604
    groupcompress is defined (currently) as reverse-topological order, grouped
 
1605
    by the key prefix.
 
1606
 
 
1607
    :return: A sorted-list of keys
 
1608
    """
 
1609
    # gc-optimal ordering is approximately reverse topological,
 
1610
    # properly grouped by file-id.
 
1611
    per_prefix_map = {}
 
1612
    for item in parent_map.iteritems():
 
1613
        key = item[0]
 
1614
        if isinstance(key, str) or len(key) == 1:
 
1615
            prefix = ''
608
1616
        else:
609
 
            # Make a new target-format versioned file. 
610
 
            temp_source = self.target.create_empty("temp", MemoryTransport())
611
 
            target = temp_source
612
 
        version_ids = self._get_source_version_ids(version_ids, ignore_missing)
613
 
        graph = self.source.get_graph(version_ids)
614
 
        order = tsort.topo_sort(graph.items())
615
 
        pb = ui.ui_factory.nested_progress_bar()
616
 
        parent_texts = {}
 
1617
            prefix = key[0]
617
1618
        try:
618
 
            # TODO for incremental cross-format work:
619
 
            # make a versioned file with the following content:
620
 
            # all revisions we have been asked to join
621
 
            # all their ancestors that are *not* in target already.
622
 
            # the immediate parents of the above two sets, with 
623
 
            # empty parent lists - these versions are in target already
624
 
            # and the incorrect version data will be ignored.
625
 
            # TODO: for all ancestors that are present in target already,
626
 
            # check them for consistent data, this requires moving sha1 from
627
 
            # 
628
 
            # TODO: remove parent texts when they are not relevant any more for 
629
 
            # memory pressure reduction. RBC 20060313
630
 
            # pb.update('Converting versioned data', 0, len(order))
631
 
            # deltas = self.source.get_deltas(order)
632
 
            for index, version in enumerate(order):
633
 
                pb.update('Converting versioned data', index, len(order))
634
 
                parent_text = target.add_lines(version,
635
 
                                               self.source.get_parents(version),
636
 
                                               self.source.get_lines(version),
637
 
                                               parent_texts=parent_texts)
638
 
                parent_texts[version] = parent_text
639
 
                #delta_parent, sha1, noeol, delta = deltas[version]
640
 
                #target.add_delta(version,
641
 
                #                 self.source.get_parents(version),
642
 
                #                 delta_parent,
643
 
                #                 sha1,
644
 
                #                 noeol,
645
 
                #                 delta)
646
 
                #target.get_lines(version)
647
 
            
648
 
            # this should hit the native code path for target
649
 
            if target is not self.target:
650
 
                return self.target.join(temp_source,
651
 
                                        pb,
652
 
                                        msg,
653
 
                                        version_ids,
654
 
                                        ignore_missing)
655
 
        finally:
656
 
            pb.finished()
657
 
 
658
 
    def _get_source_version_ids(self, version_ids, ignore_missing):
659
 
        """Determine the version ids to be used from self.source.
660
 
 
661
 
        :param version_ids: The caller-supplied version ids to check. (None 
662
 
                            for all). If None is in version_ids, it is stripped.
663
 
        :param ignore_missing: if True, remove missing ids from the version 
664
 
                               list. If False, raise RevisionNotPresent on
665
 
                               a missing version id.
666
 
        :return: A set of version ids.
667
 
        """
668
 
        if version_ids is None:
669
 
            # None cannot be in source.versions
670
 
            return set(self.source.versions())
671
 
        else:
672
 
            version_ids = [osutils.safe_revision_id(v) for v in version_ids]
673
 
            if ignore_missing:
674
 
                return set(self.source.versions()).intersection(set(version_ids))
675
 
            else:
676
 
                new_version_ids = set()
677
 
                for version in version_ids:
678
 
                    if version is None:
679
 
                        continue
680
 
                    if not self.source.has_version(version):
681
 
                        raise errors.RevisionNotPresent(version, str(self.source))
682
 
                    else:
683
 
                        new_version_ids.add(version)
684
 
                return new_version_ids
 
1619
            per_prefix_map[prefix].append(item)
 
1620
        except KeyError:
 
1621
            per_prefix_map[prefix] = [item]
 
1622
 
 
1623
    present_keys = []
 
1624
    for prefix in sorted(per_prefix_map):
 
1625
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
 
1626
    return present_keys