~bzr-pqm/bzr/bzr.dev

2520.4.85 by Aaron Bentley
Get all test passing (which just proves there aren't enough tests!)
1
# Copyright (C) 2007 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
2520.4.20 by Aaron Bentley
Compress and base64-encode bundle contents
17
from cStringIO import StringIO
2520.4.26 by Aaron Bentley
Make decompression reasonably memory-efficient
18
import bz2
2520.4.20 by Aaron Bentley
Compress and base64-encode bundle contents
19
2520.4.13 by Aaron Bentley
Use real container implementation
20
from bzrlib import (
2520.4.40 by Aaron Bentley
Add human-readable diff to bundles
21
    diff,
2520.4.34 by Aaron Bentley
Add signature support
22
    errors,
2520.4.26 by Aaron Bentley
Make decompression reasonably memory-efficient
23
    iterablefile,
2520.4.13 by Aaron Bentley
Use real container implementation
24
    multiparent,
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
25
    osutils,
2520.4.13 by Aaron Bentley
Use real container implementation
26
    pack,
2520.4.40 by Aaron Bentley
Add human-readable diff to bundles
27
    revision as _mod_revision,
2520.4.45 by Aaron Bentley
Handle inconsistencies in last-modified-revision between vf and inventory
28
    trace,
2520.4.101 by Aaron Bentley
Use a registry to look up xml serializers by format
29
    xml_serializer,
2520.4.13 by Aaron Bentley
Use real container implementation
30
    )
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
31
from bzrlib.bundle import bundle_data, serializer
2520.4.56 by Aaron Bentley
Begin adding support for arbitrary metadata
32
from bzrlib.util import bencode
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
33
2520.4.4 by Aaron Bentley
Get basis support for a new bundle format in place
34
2520.4.25 by Aaron Bentley
Rename ContainerWriter/ContainerReader to BundleWriter/BundleReader
35
class BundleWriter(object):
2520.4.118 by Aaron Bentley
Add docs
36
    """Writer for bundle-format files.
37
38
    This serves roughly the same purpose as ContainerReader, but acts as a
39
    layer on top of it.
40
2520.4.123 by Aaron Bentley
Cleanup of bundle code
41
    Provides ways of writing the specific record types supported this bundle
2520.4.118 by Aaron Bentley
Add docs
42
    format.
43
    """
2520.4.123 by Aaron Bentley
Cleanup of bundle code
44
2520.4.23 by Aaron Bentley
Move responsability for encoding into container objects
45
    def __init__(self, fileobj):
2520.4.27 by Aaron Bentley
Use less memory when writing bzip-encoded files
46
        self._container = pack.ContainerWriter(self._write_encoded)
2520.4.23 by Aaron Bentley
Move responsability for encoding into container objects
47
        self._fileobj = fileobj
2520.4.27 by Aaron Bentley
Use less memory when writing bzip-encoded files
48
        self._compressor = bz2.BZ2Compressor()
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
49
2520.4.118 by Aaron Bentley
Add docs
50
    def _write_encoded(self, bytes):
51
        """Write bzip2-encoded bytes to the file"""
52
        self._fileobj.write(self._compressor.compress(bytes))
53
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
54
    def begin(self):
2520.4.118 by Aaron Bentley
Add docs
55
        """Start writing the bundle"""
2520.4.123 by Aaron Bentley
Cleanup of bundle code
56
        self._fileobj.write(serializer._get_bundle_header(
57
            serializer.v4_string))
2520.4.24 by Aaron Bentley
Move heading writing above container beginning
58
        self._fileobj.write('#\n')
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
59
        self._container.begin()
60
61
    def end(self):
2520.4.118 by Aaron Bentley
Add docs
62
        """Finish writing the bundle"""
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
63
        self._container.end()
2520.4.76 by Aaron Bentley
Move base64-encoding into merge directives
64
        self._fileobj.write(self._compressor.flush())
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
65
2520.4.60 by Aaron Bentley
Add sha1 verification for mpdiffs
66
    def add_multiparent_record(self, mp_bytes, sha1, parents, repo_kind,
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
67
                               revision_id, file_id):
2520.4.118 by Aaron Bentley
Add docs
68
        """Add a record for a multi-parent diff
69
70
        :mp_bytes: A multi-parent diff, as a bytestring
2520.4.123 by Aaron Bentley
Cleanup of bundle code
71
        :sha1: The sha1 hash of the fulltext
2520.4.118 by Aaron Bentley
Add docs
72
        :parents: a list of revision-ids of the parents
73
        :repo_kind: The kind of object in the repository.  May be 'file' or
74
            'inventory'
75
        :revision_id: The revision id of the mpdiff being added.
76
        :file_id: The file-id of the file, or None for inventories.
77
        """
2520.4.60 by Aaron Bentley
Add sha1 verification for mpdiffs
78
        metadata = {'parents': parents,
79
                    'storage_kind': 'mpdiff',
80
                    'sha1': sha1}
81
        self._add_record(mp_bytes, metadata, repo_kind, revision_id, file_id)
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
82
2520.4.123 by Aaron Bentley
Cleanup of bundle code
83
    def add_fulltext_record(self, bytes, parents, repo_kind, revision_id):
2520.4.118 by Aaron Bentley
Add docs
84
        """Add a record for a fulltext
85
86
        :bytes: The fulltext, as a bytestring
87
        :parents: a list of revision-ids of the parents
88
        :repo_kind: The kind of object in the repository.  May be 'revision' or
89
            'signature'
90
        :revision_id: The revision id of the fulltext being added.
91
        """
92
        metadata = {'parents': parents,
2520.5.3 by Aaron Bentley
fix sha1 in bundle format 4
93
                    'storage_kind': 'mpdiff'}
2520.4.60 by Aaron Bentley
Add sha1 verification for mpdiffs
94
        self._add_record(bytes, {'parents': parents,
2520.4.123 by Aaron Bentley
Cleanup of bundle code
95
            'storage_kind': 'fulltext'}, repo_kind, revision_id, None)
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
96
2520.4.95 by Aaron Bentley
Add support for header/info records
97
    def add_info_record(self, **kwargs):
2520.4.118 by Aaron Bentley
Add docs
98
        """Add an info record to the bundle
99
100
        Any parameters may be supplied, except 'self' and 'storage_kind'.
101
        Values must be lists, strings, integers, dicts, or a combination.
102
        """
2520.4.95 by Aaron Bentley
Add support for header/info records
103
        kwargs['storage_kind'] = 'header'
104
        self._add_record(None, kwargs, 'info', None, None)
105
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
106
    @staticmethod
2520.4.68 by Aaron Bentley
Change name separators to all-slash
107
    def encode_name(content_kind, revision_id, file_id=None):
2520.4.118 by Aaron Bentley
Add docs
108
        """Encode semantic ids as a container name"""
2520.4.95 by Aaron Bentley
Add support for header/info records
109
        assert content_kind in ('revision', 'file', 'inventory', 'signature',
110
                                'info')
2520.4.118 by Aaron Bentley
Add docs
111
112
        if content_kind == 'file':
113
            assert file_id is not None
114
        else:
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
115
            assert file_id is None
2520.4.95 by Aaron Bentley
Add support for header/info records
116
        if content_kind == 'info':
117
            assert revision_id is None
118
        else:
119
            assert revision_id is not None
120
        names = [content_kind]
121
        if revision_id is not None:
122
            names.append(revision_id)
123
            if file_id is not None:
124
                names.append(file_id)
2520.4.68 by Aaron Bentley
Change name separators to all-slash
125
        return '/'.join(names)
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
126
2520.4.56 by Aaron Bentley
Begin adding support for arbitrary metadata
127
    def _add_record(self, bytes, metadata, repo_kind, revision_id, file_id):
2520.4.118 by Aaron Bentley
Add docs
128
        """Add a bundle record to the container.
129
130
        Most bundle records are recorded as header/body pairs, with the
131
        body being nameless.  Records with storage_kind 'header' have no
132
        body.
133
        """
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
134
        name = self.encode_name(repo_kind, revision_id, file_id)
2520.4.95 by Aaron Bentley
Add support for header/info records
135
        encoded_metadata = bencode.bencode(metadata)
136
        self._container.add_bytes_record(encoded_metadata, [name])
137
        if metadata['storage_kind'] != 'header':
138
            self._container.add_bytes_record(bytes, [])
2520.4.13 by Aaron Bentley
Use real container implementation
139
2520.4.7 by Aaron Bentley
Fix patch deserialization
140
2520.4.25 by Aaron Bentley
Rename ContainerWriter/ContainerReader to BundleWriter/BundleReader
141
class BundleReader(object):
2520.4.118 by Aaron Bentley
Add docs
142
    """Reader for bundle-format files.
143
144
    This serves roughly the same purpose as ContainerReader, but acts as a
145
    layer on top of it, providing metadata, a semantic name, and a record
146
    body
147
    """
2520.4.123 by Aaron Bentley
Cleanup of bundle code
148
2520.4.23 by Aaron Bentley
Move responsability for encoding into container objects
149
    def __init__(self, fileobj):
150
        line = fileobj.readline()
151
        if line != '\n':
152
            fileobj.readline()
2520.4.40 by Aaron Bentley
Add human-readable diff to bundles
153
        self.patch_lines = []
2520.4.26 by Aaron Bentley
Make decompression reasonably memory-efficient
154
        self._container = pack.ContainerReader(
2520.4.117 by Aaron Bentley
Update for new pack interface
155
            iterablefile.IterableFile(self.iter_decode(fileobj)))
2520.4.26 by Aaron Bentley
Make decompression reasonably memory-efficient
156
157
    @staticmethod
158
    def iter_decode(fileobj):
2520.4.118 by Aaron Bentley
Add docs
159
        """Iterate through decoded fragments of the file"""
2520.4.26 by Aaron Bentley
Make decompression reasonably memory-efficient
160
        decompressor = bz2.BZ2Decompressor()
161
        for line in fileobj:
2520.4.117 by Aaron Bentley
Update for new pack interface
162
            yield decompressor.decompress(line)
2520.4.22 by Aaron Bentley
Create ContainerReader
163
164
    @staticmethod
165
    def decode_name(name):
2520.4.118 by Aaron Bentley
Add docs
166
        """Decode a name from its container form into a semantic form
167
168
        :retval: content_kind, revision_id, file_id
169
        """
2520.4.68 by Aaron Bentley
Change name separators to all-slash
170
        names = name.split('/')
2520.4.95 by Aaron Bentley
Add support for header/info records
171
        content_kind = names[0]
172
        revision_id = None
173
        file_id = None
174
        if len(names) > 1:
175
            revision_id = names[1]
2520.4.68 by Aaron Bentley
Change name separators to all-slash
176
        if len(names) > 2:
177
            file_id = names[2]
178
        return content_kind, revision_id, file_id
2520.4.22 by Aaron Bentley
Create ContainerReader
179
180
    def iter_records(self):
2520.4.118 by Aaron Bentley
Add docs
181
        """Iterate through bundle records
182
183
        :return: a generator of (bytes, metadata, content_kind, revision_id,
184
            file_id)
185
        """
2520.4.69 by Aaron Bentley
Simplify encoding by storing bodies in anonymous records
186
        iterator = self._container.iter_records()
187
        for (name,), meta_bytes in iterator:
188
            metadata = bencode.bdecode(meta_bytes(None))
2520.4.95 by Aaron Bentley
Add support for header/info records
189
            if metadata['storage_kind'] == 'header':
190
                bytes = None
191
            else:
192
                _unused, bytes = iterator.next()
193
                bytes = bytes(None)
194
            yield (bytes, metadata) + self.decode_name(name)
2520.4.22 by Aaron Bentley
Create ContainerReader
195
196
2520.4.72 by Aaron Bentley
Rename format to 4alpha
197
class BundleSerializerV4(serializer.BundleSerializer):
2520.4.118 by Aaron Bentley
Add docs
198
    """Implement the high-level bundle interface"""
2520.4.123 by Aaron Bentley
Cleanup of bundle code
199
2520.4.4 by Aaron Bentley
Get basis support for a new bundle format in place
200
    def write(self, repository, revision_ids, forced_bases, fileobj):
2520.4.118 by Aaron Bentley
Add docs
201
        """Write a bundle to a file-like object
202
203
        For backwards-compatibility only
204
        """
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
205
        write_op = BundleWriteOperation.from_old_args(repository, revision_ids,
206
                                                      forced_bases, fileobj)
2520.4.53 by Aaron Bentley
refactor bundle serialization to make write_bundle primary
207
        return write_op.do_write()
208
209
    def write_bundle(self, repository, target, base, fileobj):
2520.4.118 by Aaron Bentley
Add docs
210
        """Write a bundle to a file object
211
212
        :param repository: The repository to retrieve revision data from
213
        :param target: The head revision to include ancestors of
214
        :param base: The ancestor of the target to stop including acestors
215
            at.
216
        :param fileobj: The file-like object to write to
217
        """
2520.4.53 by Aaron Bentley
refactor bundle serialization to make write_bundle primary
218
        write_op =  BundleWriteOperation(base, target, repository, fileobj)
219
        return write_op.do_write()
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
220
221
    def read(self, file):
2520.4.118 by Aaron Bentley
Add docs
222
        """return a reader object for a given file"""
2520.4.72 by Aaron Bentley
Rename format to 4alpha
223
        bundle = BundleInfoV4(file, self)
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
224
        return bundle
225
2520.4.101 by Aaron Bentley
Use a registry to look up xml serializers by format
226
    @staticmethod
227
    def get_source_serializer(info):
2520.4.118 by Aaron Bentley
Add docs
228
        """Retrieve the serializer for a given info object"""
2520.4.101 by Aaron Bentley
Use a registry to look up xml serializers by format
229
        return xml_serializer.format_registry.get(info['serializer'])
230
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
231
232
class BundleWriteOperation(object):
2520.4.118 by Aaron Bentley
Add docs
233
    """Perform the operation of writing revisions to a bundle"""
2520.4.123 by Aaron Bentley
Cleanup of bundle code
234
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
235
    @classmethod
236
    def from_old_args(cls, repository, revision_ids, forced_bases, fileobj):
2520.4.123 by Aaron Bentley
Cleanup of bundle code
237
        """Create a BundleWriteOperation from old-style arguments"""
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
238
        base, target = cls.get_base_target(revision_ids, forced_bases,
239
                                           repository)
240
        return BundleWriteOperation(base, target, repository, fileobj,
241
                                    revision_ids)
242
2520.4.53 by Aaron Bentley
refactor bundle serialization to make write_bundle primary
243
    def __init__(self, base, target, repository, fileobj, revision_ids=None):
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
244
        self.base = base
245
        self.target = target
246
        self.repository = repository
2520.4.39 by Aaron Bentley
Rename container => bundle(reader) where appropriate
247
        bundle = BundleWriter(fileobj)
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
248
        self.bundle = bundle
2520.4.64 by Aaron Bentley
Avoid topo sort for v10 bundles
249
        self.base_ancestry = set(repository.get_ancestry(base,
250
                                                         topo_sorted=False))
2520.4.53 by Aaron Bentley
refactor bundle serialization to make write_bundle primary
251
        if revision_ids is not None:
252
            self.revision_ids = revision_ids
253
        else:
2520.4.64 by Aaron Bentley
Avoid topo sort for v10 bundles
254
            revision_ids = set(repository.get_ancestry(target,
255
                                                       topo_sorted=False))
2520.4.55 by Aaron Bentley
Fix file revision selection to grab all dependencies properly
256
            self.revision_ids = revision_ids.difference(self.base_ancestry)
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
257
258
    def do_write(self):
2520.4.118 by Aaron Bentley
Add docs
259
        """Write all data to the bundle"""
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
260
        self.bundle.begin()
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
261
        self.write_info()
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
262
        self.write_files()
263
        self.write_revisions()
264
        self.bundle.end()
2520.4.53 by Aaron Bentley
refactor bundle serialization to make write_bundle primary
265
        return self.revision_ids
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
266
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
267
    def write_info(self):
2520.4.118 by Aaron Bentley
Add docs
268
        """Write format info"""
2520.4.113 by Aaron Bentley
Avoid peeking at Repository._serializer
269
        serializer_format = self.repository.get_serializer_format()
2520.4.99 by Aaron Bentley
Test conversion across models
270
        supports_rich_root = {True: 1, False: 0}[
271
            self.repository.supports_rich_root()]
2520.4.113 by Aaron Bentley
Avoid peeking at Repository._serializer
272
        self.bundle.add_info_record(serializer=serializer_format,
2520.4.99 by Aaron Bentley
Test conversion across models
273
                                    supports_rich_root=supports_rich_root)
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
274
2520.4.51 by Aaron Bentley
Split iteration through file revisions into a method, so we can vary it
275
    def iter_file_revisions(self):
2520.4.118 by Aaron Bentley
Add docs
276
        """Iterate through all relevant revisions of all files.
2520.4.51 by Aaron Bentley
Split iteration through file revisions into a method, so we can vary it
277
2520.4.118 by Aaron Bentley
Add docs
278
        This is the correct implementation, but is not compatible with bzr.dev,
279
        because certain old revisions were not converted correctly, and have
280
        the wrong "revision" marker in inventories.
2520.4.51 by Aaron Bentley
Split iteration through file revisions into a method, so we can vary it
281
        """
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
282
        transaction = self.repository.get_transaction()
283
        altered = self.repository.fileids_altered_by_revision_ids(
284
            self.revision_ids)
2520.4.6 by Aaron Bentley
Get installation started
285
        for file_id, file_revision_ids in altered.iteritems():
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
286
            vf = self.repository.weave_store.get_weave(file_id, transaction)
2520.4.51 by Aaron Bentley
Split iteration through file revisions into a method, so we can vary it
287
            yield vf, file_id, file_revision_ids
288
2520.4.55 by Aaron Bentley
Fix file revision selection to grab all dependencies properly
289
    def iter_file_revisions_aggressive(self):
2520.4.118 by Aaron Bentley
Add docs
290
        """Iterate through all relevant revisions of all files.
2520.4.55 by Aaron Bentley
Fix file revision selection to grab all dependencies properly
291
292
        This uses the standard iter_file_revisions to determine what revisions
293
        are referred to by inventories, but then uses the versionedfile to
294
        determine what the build-dependencies of each required revision.
295
296
        All build dependencies which are not ancestors of the base revision
297
        are emitted.
298
        """
299
        for vf, file_id, file_revision_ids in self.iter_file_revisions():
300
            new_revision_ids = set()
301
            pending = list(file_revision_ids)
302
            while len(pending) > 0:
303
                revision_id = pending.pop()
304
                if revision_id in new_revision_ids:
305
                    continue
306
                if revision_id in self.base_ancestry:
307
                    continue
308
                new_revision_ids.add(revision_id)
309
                pending.extend(vf.get_parents(revision_id))
310
            yield vf, file_id, new_revision_ids
311
2520.4.51 by Aaron Bentley
Split iteration through file revisions into a method, so we can vary it
312
    def write_files(self):
2520.4.118 by Aaron Bentley
Add docs
313
        """Write bundle records for all revisions of all files"""
2520.4.55 by Aaron Bentley
Fix file revision selection to grab all dependencies properly
314
        for vf, file_id, revision_ids in self.iter_file_revisions_aggressive():
2520.4.51 by Aaron Bentley
Split iteration through file revisions into a method, so we can vary it
315
            self.add_mp_records('file', file_id, vf, revision_ids)
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
316
317
    def write_revisions(self):
2520.4.118 by Aaron Bentley
Add docs
318
        """Write bundle records for all revisions and signatures"""
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
319
        inv_vf = self.repository.get_inventory_weave()
320
        revision_order = list(multiparent.topo_iter(inv_vf, self.revision_ids))
2520.4.75 by Aaron Bentley
Fix traceback on empty bundles.
321
        if self.target is not None and self.target in self.revision_ids:
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
322
            revision_order.remove(self.target)
323
            revision_order.append(self.target)
324
        self.add_mp_records('inventory', None, inv_vf, revision_order)
2520.4.114 by Aaron Bentley
Avoid deprecated method get_parent_names method
325
        parents_list = self.repository.get_parents(revision_order)
326
        for parents, revision_id in zip(parents_list, revision_order):
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
327
            revision_text = self.repository.get_revision_xml(revision_id)
328
            self.bundle.add_fulltext_record(revision_text, parents,
2520.4.123 by Aaron Bentley
Cleanup of bundle code
329
                                       'revision', revision_id)
2520.4.34 by Aaron Bentley
Add signature support
330
            try:
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
331
                self.bundle.add_fulltext_record(
332
                    self.repository.get_signature_text(
2520.4.123 by Aaron Bentley
Cleanup of bundle code
333
                    revision_id), parents, 'signature', revision_id)
2520.4.34 by Aaron Bentley
Add signature support
334
            except errors.NoSuchRevision:
335
                pass
336
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
337
    @staticmethod
338
    def get_base_target(revision_ids, forced_bases, repository):
2520.4.123 by Aaron Bentley
Cleanup of bundle code
339
        """Determine the base and target from old-style revision ids"""
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
340
        if len(revision_ids) == 0:
341
            return None, None
342
        target = revision_ids[0]
343
        base = forced_bases.get(target)
344
        if base is None:
345
            parents = repository.get_revision(target).parent_ids
346
            if len(parents) == 0:
347
                base = _mod_revision.NULL_REVISION
348
            else:
349
                base = parents[0]
350
        return base, target
351
352
    def add_mp_records(self, repo_kind, file_id, vf, revision_ids):
2520.4.118 by Aaron Bentley
Add docs
353
        """Add multi-parent diff records to a bundle"""
2520.4.41 by Aaron Bentley
Accelerate mpdiff generation
354
        revision_ids = list(multiparent.topo_iter(vf, revision_ids))
355
        mpdiffs = vf.make_mpdiffs(revision_ids)
2520.4.88 by Aaron Bentley
Retrieve all sha1s at once (ftw)
356
        sha1s = vf.get_sha1s(revision_ids)
357
        for mpdiff, revision_id, sha1, in zip(mpdiffs, revision_ids, sha1s):
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
358
            parents = vf.get_parents(revision_id)
2520.4.41 by Aaron Bentley
Accelerate mpdiff generation
359
            text = ''.join(mpdiff.to_patch())
2520.4.60 by Aaron Bentley
Add sha1 verification for mpdiffs
360
            self.bundle.add_multiparent_record(text, sha1, parents, repo_kind,
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
361
                                               revision_id, file_id)
2520.4.6 by Aaron Bentley
Get installation started
362
363
2520.4.72 by Aaron Bentley
Rename format to 4alpha
364
class BundleInfoV4(object):
2520.4.6 by Aaron Bentley
Get installation started
365
2520.4.118 by Aaron Bentley
Add docs
366
    """Provide (most of) the BundleInfo interface"""
2520.4.6 by Aaron Bentley
Get installation started
367
    def __init__(self, fileobj, serializer):
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
368
        self._fileobj = fileobj
369
        self._serializer = serializer
370
        self.__real_revisions = None
371
        self.__revisions = None
372
373
    def install(self, repository):
374
        return self.install_revisions(repository)
375
376
    def install_revisions(self, repository):
2520.4.123 by Aaron Bentley
Cleanup of bundle code
377
        """Install this bundle's revisions into the specified repository"""
2520.4.18 by Aaron Bentley
Generate mpdiffs for inventory
378
        repository.lock_write()
379
        try:
2520.4.35 by Aaron Bentley
zap obsolete changeset commands, add bundle-info command
380
            ri = RevisionInstaller(self.get_bundle_reader(),
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
381
                                   self._serializer, repository)
2520.4.18 by Aaron Bentley
Generate mpdiffs for inventory
382
            return ri.install()
383
        finally:
384
            repository.unlock()
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
385
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
386
    def get_merge_request(self, target_repo):
387
        """Provide data for performing a merge
388
389
        Returns suggested base, suggested target, and patch verification status
390
        """
391
        return None, self.target, 'inapplicable'
392
2520.4.35 by Aaron Bentley
zap obsolete changeset commands, add bundle-info command
393
    def get_bundle_reader(self):
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
394
        self._fileobj.seek(0)
2520.4.25 by Aaron Bentley
Rename ContainerWriter/ContainerReader to BundleWriter/BundleReader
395
        return BundleReader(self._fileobj)
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
396
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
397
    def _get_real_revisions(self):
398
        if self.__real_revisions is None:
399
            self.__real_revisions = []
2520.4.39 by Aaron Bentley
Rename container => bundle(reader) where appropriate
400
            bundle_reader = self.get_bundle_reader()
2520.4.102 by Aaron Bentley
rename parents to metadata
401
            for bytes, metadata, repo_kind, revision_id, file_id in \
2520.4.39 by Aaron Bentley
Rename container => bundle(reader) where appropriate
402
                bundle_reader.iter_records():
2520.4.101 by Aaron Bentley
Use a registry to look up xml serializers by format
403
                if repo_kind == 'info':
404
                    serializer =\
2520.4.102 by Aaron Bentley
rename parents to metadata
405
                        self._serializer.get_source_serializer(metadata)
2520.4.22 by Aaron Bentley
Create ContainerReader
406
                if repo_kind == 'revision':
2520.4.101 by Aaron Bentley
Use a registry to look up xml serializers by format
407
                    rev = serializer.read_revision_from_string(bytes)
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
408
                    self.__real_revisions.append(rev)
409
        return self.__real_revisions
410
    real_revisions = property(_get_real_revisions)
411
412
    def _get_revisions(self):
413
        if self.__revisions is None:
414
            self.__revisions = []
415
            for revision in self.real_revisions:
2520.4.33 by Aaron Bentley
remove test dependencies on serialization minutia
416
                self.__revisions.append(
417
                    bundle_data.RevisionInfo.from_revision(revision))
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
418
        return self.__revisions
419
420
    revisions = property(_get_revisions)
421
2520.4.29 by Aaron Bentley
Reactivate some testing, fix topo_iter
422
    def _get_target(self):
423
        return self.revisions[-1].revision_id
424
425
    target = property(_get_target)
426
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
427
428
class RevisionInstaller(object):
2520.4.123 by Aaron Bentley
Cleanup of bundle code
429
    """Installs revisions into a repository"""
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
430
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
431
    def __init__(self, container, serializer, repository):
432
        self._container = container
2520.4.6 by Aaron Bentley
Get installation started
433
        self._serializer = serializer
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
434
        self._repository = repository
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
435
        self._info = None
2520.4.99 by Aaron Bentley
Test conversion across models
436
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
437
    def install(self):
2520.4.123 by Aaron Bentley
Cleanup of bundle code
438
        """Perform the installation"""
2520.4.6 by Aaron Bentley
Get installation started
439
        current_file = None
440
        current_versionedfile = None
441
        pending_file_records = []
2520.4.8 by Aaron Bentley
Serialize inventory
442
        added_inv = set()
2520.4.29 by Aaron Bentley
Reactivate some testing, fix topo_iter
443
        target_revision = None
2520.4.58 by Aaron Bentley
Propogate support for metadata to iter_revisions, add storage kind
444
        for bytes, metadata, repo_kind, revision_id, file_id in\
2520.4.22 by Aaron Bentley
Create ContainerReader
445
            self._container.iter_records():
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
446
            if repo_kind == 'info':
447
                assert self._info is None
2520.4.123 by Aaron Bentley
Cleanup of bundle code
448
                self._handle_info(metadata)
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
449
            if repo_kind != 'file':
2520.4.18 by Aaron Bentley
Generate mpdiffs for inventory
450
                self._install_mp_records(current_versionedfile,
2520.4.6 by Aaron Bentley
Get installation started
451
                    pending_file_records)
2520.4.8 by Aaron Bentley
Serialize inventory
452
                current_file = None
453
                current_versionedfile = None
454
                pending_file_records = []
2520.4.22 by Aaron Bentley
Create ContainerReader
455
                if repo_kind == 'inventory':
2520.4.59 by Aaron Bentley
Push metadata down the stack
456
                    self._install_inventory(revision_id, metadata, bytes)
2520.4.22 by Aaron Bentley
Create ContainerReader
457
                if repo_kind == 'revision':
2520.4.28 by Aaron Bentley
Force revisions to be topologically sorted
458
                    target_revision = revision_id
2520.4.59 by Aaron Bentley
Push metadata down the stack
459
                    self._install_revision(revision_id, metadata, bytes)
2520.4.34 by Aaron Bentley
Add signature support
460
                if repo_kind == 'signature':
2520.4.59 by Aaron Bentley
Push metadata down the stack
461
                    self._install_signature(revision_id, metadata, bytes)
2520.4.22 by Aaron Bentley
Create ContainerReader
462
            if repo_kind == 'file':
2520.4.6 by Aaron Bentley
Get installation started
463
                if file_id != current_file:
2520.4.18 by Aaron Bentley
Generate mpdiffs for inventory
464
                    self._install_mp_records(current_versionedfile,
2520.4.6 by Aaron Bentley
Get installation started
465
                        pending_file_records)
466
                    current_file = file_id
467
                    current_versionedfile = \
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
468
                        self._repository.weave_store.get_weave_or_empty(
469
                        file_id, self._repository.get_transaction())
2520.4.6 by Aaron Bentley
Get installation started
470
                    pending_file_records = []
471
                if revision_id in current_versionedfile:
472
                    continue
2520.4.59 by Aaron Bentley
Push metadata down the stack
473
                pending_file_records.append((revision_id, metadata, bytes))
2520.4.18 by Aaron Bentley
Generate mpdiffs for inventory
474
        self._install_mp_records(current_versionedfile, pending_file_records)
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
475
        return target_revision
2520.4.6 by Aaron Bentley
Get installation started
476
2520.4.123 by Aaron Bentley
Cleanup of bundle code
477
    def _handle_info(self, info):
478
        """Extract data from an info record"""
479
        self._info = info
480
        self._source_serializer = self._serializer.get_source_serializer(info)
481
        if (info['supports_rich_root'] == 0 and
482
            self._repository.supports_rich_root()):
483
            self.update_root = True
484
        else:
485
            self.update_root = False
486
2520.4.60 by Aaron Bentley
Add sha1 verification for mpdiffs
487
    def _install_mp_records(self, versionedfile, records):
2520.4.61 by Aaron Bentley
Do bulk insertion of records
488
        if len(records) == 0:
489
            return
490
        d_func = multiparent.MultiParent.from_patch
491
        vf_records = [(r, m['parents'], m['sha1'], d_func(t)) for r, m, t in
492
                      records if r not in versionedfile]
493
        versionedfile.add_mpdiffs(vf_records)
2520.4.8 by Aaron Bentley
Serialize inventory
494
2520.4.59 by Aaron Bentley
Push metadata down the stack
495
    def _install_inventory(self, revision_id, metadata, text):
2520.4.18 by Aaron Bentley
Generate mpdiffs for inventory
496
        vf = self._repository.get_inventory_weave()
2520.4.99 by Aaron Bentley
Test conversion across models
497
        if revision_id in vf:
498
            return
499
        parent_ids = metadata['parents']
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
500
        if self._info['serializer'] == self._repository._serializer.format_num:
501
            return self._install_mp_records(vf, [(revision_id, metadata,
502
                                                  text)])
503
        parents = [self._repository.get_inventory(p)
2520.4.99 by Aaron Bentley
Test conversion across models
504
                   for p in parent_ids]
2520.4.98 by Aaron Bentley
Support inventory conversion with parents
505
        parent_texts = [self._source_serializer.write_inventory_to_string(p)
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
506
                        for p in parents]
2520.4.103 by Aaron Bentley
Add MultiParent.to_lines
507
        target_lines = multiparent.MultiParent.from_patch(text).to_lines(
508
            parent_texts)
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
509
        sha1 = osutils.sha_strings(target_lines)
510
        if sha1 != metadata['sha1']:
511
            raise BadBundle("Can't convert to target format")
512
        target_inv = self._source_serializer.read_inventory_from_string(
513
            ''.join(target_lines))
2520.4.99 by Aaron Bentley
Test conversion across models
514
        self._handle_root(target_inv, parent_ids)
2520.5.1 by Aaron Bentley
Test installing revisions with subtrees
515
        try:
516
            self._repository.add_inventory(revision_id, target_inv, parent_ids)
517
        except errors.UnsupportedInventoryKind:
518
            raise errors.IncompatibleRevision(repr(self._repository))
2520.4.99 by Aaron Bentley
Test conversion across models
519
520
    def _handle_root(self, target_inv, parent_ids):
521
        revision_id = target_inv.revision_id
522
        if self.update_root:
523
            target_inv.root.revision = revision_id
524
            store = self._repository.weave_store
525
            transaction = self._repository.get_transaction()
526
            vf = store.get_weave_or_empty(target_inv.root.file_id, transaction)
527
            vf.add_lines(revision_id, parent_ids, [])
528
        elif not self._repository.supports_rich_root():
529
            if target_inv.root.revision != revision_id:
530
                raise errors.IncompatibleRevision(repr(self._repository))
531
2520.4.10 by Aaron Bentley
Enable installation of revisions
532
2520.4.59 by Aaron Bentley
Push metadata down the stack
533
    def _install_revision(self, revision_id, metadata, text):
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
534
        if self._repository.has_revision(revision_id):
535
            return
536
        self._repository._add_revision_text(revision_id, text)
2520.4.34 by Aaron Bentley
Add signature support
537
2520.4.59 by Aaron Bentley
Push metadata down the stack
538
    def _install_signature(self, revision_id, metadata, text):
2520.4.100 by Aaron Bentley
Fix repeat signature installs
539
        transaction = self._repository.get_transaction()
540
        if self._repository._revision_store.has_signature(revision_id,
541
                                                          transaction):
542
            return
2520.4.34 by Aaron Bentley
Add signature support
543
        self._repository._revision_store.add_revision_signature_text(
2520.4.100 by Aaron Bentley
Fix repeat signature installs
544
            revision_id, text, transaction)