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