/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
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)
134
        self._container.add_bytes_record(encoded_metadata, [name])
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.23 by Aaron Bentley
Move responsability for encoding into container objects
147
    def __init__(self, fileobj):
148
        line = fileobj.readline()
149
        if line != '\n':
150
            fileobj.readline()
2520.4.40 by Aaron Bentley
Add human-readable diff to bundles
151
        self.patch_lines = []
2520.4.26 by Aaron Bentley
Make decompression reasonably memory-efficient
152
        self._container = pack.ContainerReader(
2520.4.117 by Aaron Bentley
Update for new pack interface
153
            iterablefile.IterableFile(self.iter_decode(fileobj)))
2520.4.26 by Aaron Bentley
Make decompression reasonably memory-efficient
154
155
    @staticmethod
156
    def iter_decode(fileobj):
2520.4.118 by Aaron Bentley
Add docs
157
        """Iterate through decoded fragments of the file"""
2520.4.26 by Aaron Bentley
Make decompression reasonably memory-efficient
158
        decompressor = bz2.BZ2Decompressor()
159
        for line in fileobj:
2520.4.117 by Aaron Bentley
Update for new pack interface
160
            yield decompressor.decompress(line)
2520.4.22 by Aaron Bentley
Create ContainerReader
161
162
    @staticmethod
163
    def decode_name(name):
2520.4.118 by Aaron Bentley
Add docs
164
        """Decode a name from its container form into a semantic form
165
166
        :retval: content_kind, revision_id, file_id
167
        """
2520.4.130 by Aaron Bentley
Finish tweaking decode_name
168
        segments = re.split('(//?)', name)
169
        names = ['']
2520.4.127 by Aaron Bentley
Fix up name encoding to handle revision-ids with slashes
170
        for segment in segments:
171
            if segment == '//':
172
                names[-1] += '/'
2520.4.130 by Aaron Bentley
Finish tweaking decode_name
173
            elif segment == '/':
2520.4.127 by Aaron Bentley
Fix up name encoding to handle revision-ids with slashes
174
                names.append('')
175
            else:
176
                names[-1] += segment
2520.4.130 by Aaron Bentley
Finish tweaking decode_name
177
        content_kind = names[0]
2520.4.95 by Aaron Bentley
Add support for header/info records
178
        revision_id = None
179
        file_id = None
180
        if len(names) > 1:
181
            revision_id = names[1]
2520.4.68 by Aaron Bentley
Change name separators to all-slash
182
        if len(names) > 2:
183
            file_id = names[2]
184
        return content_kind, revision_id, file_id
2520.4.22 by Aaron Bentley
Create ContainerReader
185
186
    def iter_records(self):
2520.4.118 by Aaron Bentley
Add docs
187
        """Iterate through bundle records
188
189
        :return: a generator of (bytes, metadata, content_kind, revision_id,
190
            file_id)
191
        """
2520.4.69 by Aaron Bentley
Simplify encoding by storing bodies in anonymous records
192
        iterator = self._container.iter_records()
2520.4.131 by Aaron Bentley
Raise BadBundle for records with wrong number of names
193
        for names, meta_bytes in iterator:
194
            if len(names) != 1:
195
                raise errors.BadBundle('Record has %d names instead of 1'
196
                                       % len(names))
2520.4.69 by Aaron Bentley
Simplify encoding by storing bodies in anonymous records
197
            metadata = bencode.bdecode(meta_bytes(None))
2520.4.95 by Aaron Bentley
Add support for header/info records
198
            if metadata['storage_kind'] == 'header':
199
                bytes = None
200
            else:
201
                _unused, bytes = iterator.next()
202
                bytes = bytes(None)
2520.4.131 by Aaron Bentley
Raise BadBundle for records with wrong number of names
203
            yield (bytes, metadata) + self.decode_name(names[0])
2520.4.22 by Aaron Bentley
Create ContainerReader
204
205
2520.4.72 by Aaron Bentley
Rename format to 4alpha
206
class BundleSerializerV4(serializer.BundleSerializer):
2520.4.118 by Aaron Bentley
Add docs
207
    """Implement the high-level bundle interface"""
2520.4.123 by Aaron Bentley
Cleanup of bundle code
208
2520.4.4 by Aaron Bentley
Get basis support for a new bundle format in place
209
    def write(self, repository, revision_ids, forced_bases, fileobj):
2520.4.118 by Aaron Bentley
Add docs
210
        """Write a bundle to a file-like object
211
212
        For backwards-compatibility only
213
        """
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
214
        write_op = BundleWriteOperation.from_old_args(repository, revision_ids,
215
                                                      forced_bases, fileobj)
2520.4.53 by Aaron Bentley
refactor bundle serialization to make write_bundle primary
216
        return write_op.do_write()
217
218
    def write_bundle(self, repository, target, base, fileobj):
2520.4.118 by Aaron Bentley
Add docs
219
        """Write a bundle to a file object
220
221
        :param repository: The repository to retrieve revision data from
222
        :param target: The head revision to include ancestors of
223
        :param base: The ancestor of the target to stop including acestors
224
            at.
225
        :param fileobj: The file-like object to write to
226
        """
2520.4.53 by Aaron Bentley
refactor bundle serialization to make write_bundle primary
227
        write_op =  BundleWriteOperation(base, target, repository, fileobj)
228
        return write_op.do_write()
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
229
230
    def read(self, file):
2520.4.118 by Aaron Bentley
Add docs
231
        """return a reader object for a given file"""
2520.4.72 by Aaron Bentley
Rename format to 4alpha
232
        bundle = BundleInfoV4(file, self)
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
233
        return bundle
234
2520.4.101 by Aaron Bentley
Use a registry to look up xml serializers by format
235
    @staticmethod
236
    def get_source_serializer(info):
2520.4.118 by Aaron Bentley
Add docs
237
        """Retrieve the serializer for a given info object"""
2520.4.101 by Aaron Bentley
Use a registry to look up xml serializers by format
238
        return xml_serializer.format_registry.get(info['serializer'])
239
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
240
241
class BundleWriteOperation(object):
2520.4.118 by Aaron Bentley
Add docs
242
    """Perform the operation of writing revisions to a bundle"""
2520.4.123 by Aaron Bentley
Cleanup of bundle code
243
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
244
    @classmethod
245
    def from_old_args(cls, repository, revision_ids, forced_bases, fileobj):
2520.4.123 by Aaron Bentley
Cleanup of bundle code
246
        """Create a BundleWriteOperation from old-style arguments"""
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
247
        base, target = cls.get_base_target(revision_ids, forced_bases,
248
                                           repository)
249
        return BundleWriteOperation(base, target, repository, fileobj,
250
                                    revision_ids)
251
2520.4.53 by Aaron Bentley
refactor bundle serialization to make write_bundle primary
252
    def __init__(self, base, target, repository, fileobj, revision_ids=None):
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
253
        self.base = base
254
        self.target = target
255
        self.repository = repository
2520.4.39 by Aaron Bentley
Rename container => bundle(reader) where appropriate
256
        bundle = BundleWriter(fileobj)
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
257
        self.bundle = bundle
2520.4.64 by Aaron Bentley
Avoid topo sort for v10 bundles
258
        self.base_ancestry = set(repository.get_ancestry(base,
259
                                                         topo_sorted=False))
2520.4.53 by Aaron Bentley
refactor bundle serialization to make write_bundle primary
260
        if revision_ids is not None:
261
            self.revision_ids = revision_ids
262
        else:
2520.4.64 by Aaron Bentley
Avoid topo sort for v10 bundles
263
            revision_ids = set(repository.get_ancestry(target,
264
                                                       topo_sorted=False))
2520.4.55 by Aaron Bentley
Fix file revision selection to grab all dependencies properly
265
            self.revision_ids = revision_ids.difference(self.base_ancestry)
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
266
267
    def do_write(self):
2520.4.118 by Aaron Bentley
Add docs
268
        """Write all data to the bundle"""
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
269
        self.bundle.begin()
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
270
        self.write_info()
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
271
        self.write_files()
272
        self.write_revisions()
273
        self.bundle.end()
2520.4.53 by Aaron Bentley
refactor bundle serialization to make write_bundle primary
274
        return self.revision_ids
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
275
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
276
    def write_info(self):
2520.4.118 by Aaron Bentley
Add docs
277
        """Write format info"""
2520.4.113 by Aaron Bentley
Avoid peeking at Repository._serializer
278
        serializer_format = self.repository.get_serializer_format()
2520.4.99 by Aaron Bentley
Test conversion across models
279
        supports_rich_root = {True: 1, False: 0}[
280
            self.repository.supports_rich_root()]
2520.4.113 by Aaron Bentley
Avoid peeking at Repository._serializer
281
        self.bundle.add_info_record(serializer=serializer_format,
2520.4.99 by Aaron Bentley
Test conversion across models
282
                                    supports_rich_root=supports_rich_root)
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
283
2520.4.51 by Aaron Bentley
Split iteration through file revisions into a method, so we can vary it
284
    def iter_file_revisions(self):
2520.4.118 by Aaron Bentley
Add docs
285
        """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
286
2520.4.118 by Aaron Bentley
Add docs
287
        This is the correct implementation, but is not compatible with bzr.dev,
288
        because certain old revisions were not converted correctly, and have
289
        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
290
        """
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
291
        transaction = self.repository.get_transaction()
292
        altered = self.repository.fileids_altered_by_revision_ids(
293
            self.revision_ids)
2520.4.6 by Aaron Bentley
Get installation started
294
        for file_id, file_revision_ids in altered.iteritems():
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
295
            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
296
            yield vf, file_id, file_revision_ids
297
2520.4.55 by Aaron Bentley
Fix file revision selection to grab all dependencies properly
298
    def iter_file_revisions_aggressive(self):
2520.4.118 by Aaron Bentley
Add docs
299
        """Iterate through all relevant revisions of all files.
2520.4.55 by Aaron Bentley
Fix file revision selection to grab all dependencies properly
300
301
        This uses the standard iter_file_revisions to determine what revisions
302
        are referred to by inventories, but then uses the versionedfile to
303
        determine what the build-dependencies of each required revision.
304
305
        All build dependencies which are not ancestors of the base revision
306
        are emitted.
307
        """
308
        for vf, file_id, file_revision_ids in self.iter_file_revisions():
309
            new_revision_ids = set()
310
            pending = list(file_revision_ids)
311
            while len(pending) > 0:
312
                revision_id = pending.pop()
313
                if revision_id in new_revision_ids:
314
                    continue
315
                if revision_id in self.base_ancestry:
316
                    continue
317
                new_revision_ids.add(revision_id)
318
                pending.extend(vf.get_parents(revision_id))
319
            yield vf, file_id, new_revision_ids
320
2520.4.51 by Aaron Bentley
Split iteration through file revisions into a method, so we can vary it
321
    def write_files(self):
2520.4.118 by Aaron Bentley
Add docs
322
        """Write bundle records for all revisions of all files"""
2520.4.55 by Aaron Bentley
Fix file revision selection to grab all dependencies properly
323
        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
324
            self.add_mp_records('file', file_id, vf, revision_ids)
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
325
326
    def write_revisions(self):
2520.4.118 by Aaron Bentley
Add docs
327
        """Write bundle records for all revisions and signatures"""
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
328
        inv_vf = self.repository.get_inventory_weave()
329
        revision_order = list(multiparent.topo_iter(inv_vf, self.revision_ids))
2520.4.75 by Aaron Bentley
Fix traceback on empty bundles.
330
        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
331
            revision_order.remove(self.target)
332
            revision_order.append(self.target)
333
        self.add_mp_records('inventory', None, inv_vf, revision_order)
2520.4.114 by Aaron Bentley
Avoid deprecated method get_parent_names method
334
        parents_list = self.repository.get_parents(revision_order)
335
        for parents, revision_id in zip(parents_list, revision_order):
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
336
            revision_text = self.repository.get_revision_xml(revision_id)
337
            self.bundle.add_fulltext_record(revision_text, parents,
2520.4.123 by Aaron Bentley
Cleanup of bundle code
338
                                       'revision', revision_id)
2520.4.34 by Aaron Bentley
Add signature support
339
            try:
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
340
                self.bundle.add_fulltext_record(
341
                    self.repository.get_signature_text(
2520.4.123 by Aaron Bentley
Cleanup of bundle code
342
                    revision_id), parents, 'signature', revision_id)
2520.4.34 by Aaron Bentley
Add signature support
343
            except errors.NoSuchRevision:
344
                pass
345
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
346
    @staticmethod
347
    def get_base_target(revision_ids, forced_bases, repository):
2520.4.123 by Aaron Bentley
Cleanup of bundle code
348
        """Determine the base and target from old-style revision ids"""
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
349
        if len(revision_ids) == 0:
350
            return None, None
351
        target = revision_ids[0]
352
        base = forced_bases.get(target)
353
        if base is None:
354
            parents = repository.get_revision(target).parent_ids
355
            if len(parents) == 0:
356
                base = _mod_revision.NULL_REVISION
357
            else:
358
                base = parents[0]
359
        return base, target
360
361
    def add_mp_records(self, repo_kind, file_id, vf, revision_ids):
2520.4.118 by Aaron Bentley
Add docs
362
        """Add multi-parent diff records to a bundle"""
2520.4.41 by Aaron Bentley
Accelerate mpdiff generation
363
        revision_ids = list(multiparent.topo_iter(vf, revision_ids))
364
        mpdiffs = vf.make_mpdiffs(revision_ids)
2520.4.88 by Aaron Bentley
Retrieve all sha1s at once (ftw)
365
        sha1s = vf.get_sha1s(revision_ids)
366
        for mpdiff, revision_id, sha1, in zip(mpdiffs, revision_ids, sha1s):
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
367
            parents = vf.get_parents(revision_id)
2520.4.41 by Aaron Bentley
Accelerate mpdiff generation
368
            text = ''.join(mpdiff.to_patch())
2520.4.60 by Aaron Bentley
Add sha1 verification for mpdiffs
369
            self.bundle.add_multiparent_record(text, sha1, parents, repo_kind,
2520.4.50 by Aaron Bentley
Split write functionality out into a separate object
370
                                               revision_id, file_id)
2520.4.6 by Aaron Bentley
Get installation started
371
372
2520.4.72 by Aaron Bentley
Rename format to 4alpha
373
class BundleInfoV4(object):
2520.4.6 by Aaron Bentley
Get installation started
374
2520.4.118 by Aaron Bentley
Add docs
375
    """Provide (most of) the BundleInfo interface"""
2520.4.6 by Aaron Bentley
Get installation started
376
    def __init__(self, fileobj, serializer):
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
377
        self._fileobj = fileobj
378
        self._serializer = serializer
379
        self.__real_revisions = None
380
        self.__revisions = None
381
382
    def install(self, repository):
383
        return self.install_revisions(repository)
384
385
    def install_revisions(self, repository):
2520.4.123 by Aaron Bentley
Cleanup of bundle code
386
        """Install this bundle's revisions into the specified repository"""
2520.4.18 by Aaron Bentley
Generate mpdiffs for inventory
387
        repository.lock_write()
388
        try:
2520.4.35 by Aaron Bentley
zap obsolete changeset commands, add bundle-info command
389
            ri = RevisionInstaller(self.get_bundle_reader(),
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
390
                                   self._serializer, repository)
2520.4.18 by Aaron Bentley
Generate mpdiffs for inventory
391
            return ri.install()
392
        finally:
393
            repository.unlock()
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
394
2520.4.109 by Aaron Bentley
start work on directive cherry-picking
395
    def get_merge_request(self, target_repo):
396
        """Provide data for performing a merge
397
398
        Returns suggested base, suggested target, and patch verification status
399
        """
400
        return None, self.target, 'inapplicable'
401
2520.4.35 by Aaron Bentley
zap obsolete changeset commands, add bundle-info command
402
    def get_bundle_reader(self):
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
403
        self._fileobj.seek(0)
2520.4.25 by Aaron Bentley
Rename ContainerWriter/ContainerReader to BundleWriter/BundleReader
404
        return BundleReader(self._fileobj)
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
405
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
406
    def _get_real_revisions(self):
407
        if self.__real_revisions is None:
408
            self.__real_revisions = []
2520.4.39 by Aaron Bentley
Rename container => bundle(reader) where appropriate
409
            bundle_reader = self.get_bundle_reader()
2520.4.102 by Aaron Bentley
rename parents to metadata
410
            for bytes, metadata, repo_kind, revision_id, file_id in \
2520.4.39 by Aaron Bentley
Rename container => bundle(reader) where appropriate
411
                bundle_reader.iter_records():
2520.4.101 by Aaron Bentley
Use a registry to look up xml serializers by format
412
                if repo_kind == 'info':
413
                    serializer =\
2520.4.102 by Aaron Bentley
rename parents to metadata
414
                        self._serializer.get_source_serializer(metadata)
2520.4.22 by Aaron Bentley
Create ContainerReader
415
                if repo_kind == 'revision':
2520.4.101 by Aaron Bentley
Use a registry to look up xml serializers by format
416
                    rev = serializer.read_revision_from_string(bytes)
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
417
                    self.__real_revisions.append(rev)
418
        return self.__real_revisions
419
    real_revisions = property(_get_real_revisions)
420
421
    def _get_revisions(self):
422
        if self.__revisions is None:
423
            self.__revisions = []
424
            for revision in self.real_revisions:
2520.4.33 by Aaron Bentley
remove test dependencies on serialization minutia
425
                self.__revisions.append(
426
                    bundle_data.RevisionInfo.from_revision(revision))
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
427
        return self.__revisions
428
429
    revisions = property(_get_revisions)
430
2520.4.29 by Aaron Bentley
Reactivate some testing, fix topo_iter
431
    def _get_target(self):
432
        return self.revisions[-1].revision_id
433
434
    target = property(_get_target)
435
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
436
437
class RevisionInstaller(object):
2520.4.123 by Aaron Bentley
Cleanup of bundle code
438
    """Installs revisions into a repository"""
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
439
2520.4.21 by Aaron Bentley
Finish turning ContainerWriter into a new layer
440
    def __init__(self, container, serializer, repository):
441
        self._container = container
2520.4.6 by Aaron Bentley
Get installation started
442
        self._serializer = serializer
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
443
        self._repository = repository
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
444
        self._info = None
2520.4.99 by Aaron Bentley
Test conversion across models
445
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
446
    def install(self):
2520.4.123 by Aaron Bentley
Cleanup of bundle code
447
        """Perform the installation"""
2520.4.6 by Aaron Bentley
Get installation started
448
        current_file = None
449
        current_versionedfile = None
450
        pending_file_records = []
2520.4.8 by Aaron Bentley
Serialize inventory
451
        added_inv = set()
2520.4.29 by Aaron Bentley
Reactivate some testing, fix topo_iter
452
        target_revision = None
2520.4.58 by Aaron Bentley
Propogate support for metadata to iter_revisions, add storage kind
453
        for bytes, metadata, repo_kind, revision_id, file_id in\
2520.4.22 by Aaron Bentley
Create ContainerReader
454
            self._container.iter_records():
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
455
            if repo_kind == 'info':
456
                assert self._info is None
2520.4.123 by Aaron Bentley
Cleanup of bundle code
457
                self._handle_info(metadata)
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
458
            if repo_kind != 'file':
2520.4.18 by Aaron Bentley
Generate mpdiffs for inventory
459
                self._install_mp_records(current_versionedfile,
2520.4.6 by Aaron Bentley
Get installation started
460
                    pending_file_records)
2520.4.8 by Aaron Bentley
Serialize inventory
461
                current_file = None
462
                current_versionedfile = None
463
                pending_file_records = []
2520.4.22 by Aaron Bentley
Create ContainerReader
464
                if repo_kind == 'inventory':
2520.4.59 by Aaron Bentley
Push metadata down the stack
465
                    self._install_inventory(revision_id, metadata, bytes)
2520.4.22 by Aaron Bentley
Create ContainerReader
466
                if repo_kind == 'revision':
2520.4.28 by Aaron Bentley
Force revisions to be topologically sorted
467
                    target_revision = revision_id
2520.4.59 by Aaron Bentley
Push metadata down the stack
468
                    self._install_revision(revision_id, metadata, bytes)
2520.4.34 by Aaron Bentley
Add signature support
469
                if repo_kind == 'signature':
2520.4.59 by Aaron Bentley
Push metadata down the stack
470
                    self._install_signature(revision_id, metadata, bytes)
2520.4.22 by Aaron Bentley
Create ContainerReader
471
            if repo_kind == 'file':
2520.4.6 by Aaron Bentley
Get installation started
472
                if file_id != current_file:
2520.4.18 by Aaron Bentley
Generate mpdiffs for inventory
473
                    self._install_mp_records(current_versionedfile,
2520.4.6 by Aaron Bentley
Get installation started
474
                        pending_file_records)
475
                    current_file = file_id
476
                    current_versionedfile = \
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
477
                        self._repository.weave_store.get_weave_or_empty(
478
                        file_id, self._repository.get_transaction())
2520.4.6 by Aaron Bentley
Get installation started
479
                    pending_file_records = []
480
                if revision_id in current_versionedfile:
481
                    continue
2520.4.59 by Aaron Bentley
Push metadata down the stack
482
                pending_file_records.append((revision_id, metadata, bytes))
2520.4.18 by Aaron Bentley
Generate mpdiffs for inventory
483
        self._install_mp_records(current_versionedfile, pending_file_records)
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
484
        return target_revision
2520.4.6 by Aaron Bentley
Get installation started
485
2520.4.123 by Aaron Bentley
Cleanup of bundle code
486
    def _handle_info(self, info):
487
        """Extract data from an info record"""
488
        self._info = info
489
        self._source_serializer = self._serializer.get_source_serializer(info)
490
        if (info['supports_rich_root'] == 0 and
491
            self._repository.supports_rich_root()):
492
            self.update_root = True
493
        else:
494
            self.update_root = False
495
2520.4.60 by Aaron Bentley
Add sha1 verification for mpdiffs
496
    def _install_mp_records(self, versionedfile, records):
2520.4.61 by Aaron Bentley
Do bulk insertion of records
497
        if len(records) == 0:
498
            return
499
        d_func = multiparent.MultiParent.from_patch
500
        vf_records = [(r, m['parents'], m['sha1'], d_func(t)) for r, m, t in
501
                      records if r not in versionedfile]
502
        versionedfile.add_mpdiffs(vf_records)
2520.4.8 by Aaron Bentley
Serialize inventory
503
2520.4.59 by Aaron Bentley
Push metadata down the stack
504
    def _install_inventory(self, revision_id, metadata, text):
2520.4.18 by Aaron Bentley
Generate mpdiffs for inventory
505
        vf = self._repository.get_inventory_weave()
2520.4.99 by Aaron Bentley
Test conversion across models
506
        if revision_id in vf:
507
            return
508
        parent_ids = metadata['parents']
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
509
        if self._info['serializer'] == self._repository._serializer.format_num:
510
            return self._install_mp_records(vf, [(revision_id, metadata,
511
                                                  text)])
512
        parents = [self._repository.get_inventory(p)
2520.4.99 by Aaron Bentley
Test conversion across models
513
                   for p in parent_ids]
2520.4.98 by Aaron Bentley
Support inventory conversion with parents
514
        parent_texts = [self._source_serializer.write_inventory_to_string(p)
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
515
                        for p in parents]
2520.4.103 by Aaron Bentley
Add MultiParent.to_lines
516
        target_lines = multiparent.MultiParent.from_patch(text).to_lines(
517
            parent_texts)
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
518
        sha1 = osutils.sha_strings(target_lines)
519
        if sha1 != metadata['sha1']:
2520.4.131 by Aaron Bentley
Raise BadBundle for records with wrong number of names
520
            raise errors.BadBundle("Can't convert to target format")
2520.4.97 by Aaron Bentley
Hack in support for inventory conversion
521
        target_inv = self._source_serializer.read_inventory_from_string(
522
            ''.join(target_lines))
2520.4.99 by Aaron Bentley
Test conversion across models
523
        self._handle_root(target_inv, parent_ids)
2520.5.1 by Aaron Bentley
Test installing revisions with subtrees
524
        try:
525
            self._repository.add_inventory(revision_id, target_inv, parent_ids)
526
        except errors.UnsupportedInventoryKind:
527
            raise errors.IncompatibleRevision(repr(self._repository))
2520.4.99 by Aaron Bentley
Test conversion across models
528
529
    def _handle_root(self, target_inv, parent_ids):
530
        revision_id = target_inv.revision_id
531
        if self.update_root:
532
            target_inv.root.revision = revision_id
533
            store = self._repository.weave_store
534
            transaction = self._repository.get_transaction()
535
            vf = store.get_weave_or_empty(target_inv.root.file_id, transaction)
536
            vf.add_lines(revision_id, parent_ids, [])
537
        elif not self._repository.supports_rich_root():
538
            if target_inv.root.revision != revision_id:
539
                raise errors.IncompatibleRevision(repr(self._repository))
540
2520.4.10 by Aaron Bentley
Enable installation of revisions
541
2520.4.59 by Aaron Bentley
Push metadata down the stack
542
    def _install_revision(self, revision_id, metadata, text):
2520.4.14 by Aaron Bentley
Get most tests passing, use format header
543
        if self._repository.has_revision(revision_id):
544
            return
545
        self._repository._add_revision_text(revision_id, text)
2520.4.34 by Aaron Bentley
Add signature support
546
2520.4.59 by Aaron Bentley
Push metadata down the stack
547
    def _install_signature(self, revision_id, metadata, text):
2520.4.100 by Aaron Bentley
Fix repeat signature installs
548
        transaction = self._repository.get_transaction()
549
        if self._repository._revision_store.has_signature(revision_id,
550
                                                          transaction):
551
            return
2520.4.34 by Aaron Bentley
Add signature support
552
        self._repository._revision_store.add_revision_signature_text(
2520.4.100 by Aaron Bentley
Fix repeat signature installs
553
            revision_id, text, transaction)