~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/serializer/v4.py

Merge doc updates

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
 
35
35
class BundleWriter(object):
36
36
 
 
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
 
 
42
    Provides ways of writing the spcific record types supported this bundle
 
43
    format.
 
44
    """
37
45
    def __init__(self, fileobj):
38
46
        self._container = pack.ContainerWriter(self._write_encoded)
39
47
        self._fileobj = fileobj
40
48
        self._compressor = bz2.BZ2Compressor()
41
49
 
 
50
    def _write_encoded(self, bytes):
 
51
        """Write bzip2-encoded bytes to the file"""
 
52
        self._fileobj.write(self._compressor.compress(bytes))
 
53
 
42
54
    def begin(self):
 
55
        """Start writing the bundle"""
43
56
        self._fileobj.write(serializer._get_bundle_header('4alpha'))
44
57
        self._fileobj.write('#\n')
45
58
        self._container.begin()
46
59
 
47
 
    def _write_encoded(self, bytes):
48
 
        self._fileobj.write(self._compressor.compress(bytes))
49
 
 
50
60
    def end(self):
 
61
        """Finish writing the bundle"""
51
62
        self._container.end()
52
63
        self._fileobj.write(self._compressor.flush())
53
64
 
54
65
    def add_multiparent_record(self, mp_bytes, sha1, parents, repo_kind,
55
66
                               revision_id, file_id):
 
67
        """Add a record for a multi-parent diff
 
68
 
 
69
        :mp_bytes: A multi-parent diff, as a bytestring
 
70
        :parents: a list of revision-ids of the parents
 
71
        :repo_kind: The kind of object in the repository.  May be 'file' or
 
72
            'inventory'
 
73
        :revision_id: The revision id of the mpdiff being added.
 
74
        :file_id: The file-id of the file, or None for inventories.
 
75
        """
56
76
        metadata = {'parents': parents,
57
77
                    'storage_kind': 'mpdiff',
58
78
                    'sha1': sha1}
60
80
 
61
81
    def add_fulltext_record(self, bytes, parents, repo_kind, revision_id,
62
82
                            file_id):
 
83
        """Add a record for a fulltext
 
84
 
 
85
        :bytes: The fulltext, as a bytestring
 
86
        :parents: a list of revision-ids of the parents
 
87
        :repo_kind: The kind of object in the repository.  May be 'revision' or
 
88
            'signature'
 
89
        :revision_id: The revision id of the fulltext being added.
 
90
        :file_id: must be None
 
91
        """
 
92
        metadata = {'parents': parents,
 
93
                    'storage_kind': 'mpdiff',
 
94
                    'sha1': sha1}
63
95
        self._add_record(bytes, {'parents': parents,
64
96
            'storage_kind': 'fulltext'}, repo_kind, revision_id, file_id)
65
97
 
66
98
    def add_info_record(self, **kwargs):
 
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
        """
67
104
        kwargs['storage_kind'] = 'header'
68
105
        self._add_record(None, kwargs, 'info', None, None)
69
106
 
70
107
    @staticmethod
71
108
    def encode_name(content_kind, revision_id, file_id=None):
 
109
        """Encode semantic ids as a container name"""
72
110
        assert content_kind in ('revision', 'file', 'inventory', 'signature',
73
111
                                'info')
74
 
        if content_kind in ('revision', 'inventory', 'signature', 'info'):
 
112
 
 
113
        if content_kind == 'file':
 
114
            assert file_id is not None
 
115
        else:
75
116
            assert file_id is None
76
 
        else:
77
 
            assert file_id is not None
78
117
        if content_kind == 'info':
79
118
            assert revision_id is None
80
119
        else:
87
126
        return '/'.join(names)
88
127
 
89
128
    def _add_record(self, bytes, metadata, repo_kind, revision_id, file_id):
 
129
        """Add a bundle record to the container.
 
130
 
 
131
        Most bundle records are recorded as header/body pairs, with the
 
132
        body being nameless.  Records with storage_kind 'header' have no
 
133
        body.
 
134
        """
90
135
        name = self.encode_name(repo_kind, revision_id, file_id)
91
136
        encoded_metadata = bencode.bencode(metadata)
92
137
        self._container.add_bytes_record(encoded_metadata, [name])
96
141
 
97
142
class BundleReader(object):
98
143
 
 
144
    """Reader for bundle-format files.
 
145
 
 
146
    This serves roughly the same purpose as ContainerReader, but acts as a
 
147
    layer on top of it, providing metadata, a semantic name, and a record
 
148
    body
 
149
    """
99
150
    def __init__(self, fileobj):
100
151
        line = fileobj.readline()
101
152
        if line != '\n':
106
157
 
107
158
    @staticmethod
108
159
    def iter_decode(fileobj):
 
160
        """Iterate through decoded fragments of the file"""
109
161
        decompressor = bz2.BZ2Decompressor()
110
162
        for line in fileobj:
111
163
            yield decompressor.decompress(line)
112
164
 
113
165
    @staticmethod
114
166
    def decode_name(name):
 
167
        """Decode a name from its container form into a semantic form
 
168
 
 
169
        :retval: content_kind, revision_id, file_id
 
170
        """
115
171
        names = name.split('/')
116
172
        content_kind = names[0]
117
173
        revision_id = None
123
179
        return content_kind, revision_id, file_id
124
180
 
125
181
    def iter_records(self):
 
182
        """Iterate through bundle records
 
183
 
 
184
        :return: a generator of (bytes, metadata, content_kind, revision_id,
 
185
            file_id)
 
186
        """
126
187
        iterator = self._container.iter_records()
127
188
        for (name,), meta_bytes in iterator:
128
189
            metadata = bencode.bdecode(meta_bytes(None))
136
197
 
137
198
class BundleSerializerV4(serializer.BundleSerializer):
138
199
 
 
200
    """Implement the high-level bundle interface"""
139
201
    def write(self, repository, revision_ids, forced_bases, fileobj):
 
202
        """Write a bundle to a file-like object
 
203
 
 
204
        For backwards-compatibility only
 
205
        """
140
206
        write_op = BundleWriteOperation.from_old_args(repository, revision_ids,
141
207
                                                      forced_bases, fileobj)
142
208
        return write_op.do_write()
143
209
 
144
210
    def write_bundle(self, repository, target, base, fileobj):
 
211
        """Write a bundle to a file object
 
212
 
 
213
        :param repository: The repository to retrieve revision data from
 
214
        :param target: The head revision to include ancestors of
 
215
        :param base: The ancestor of the target to stop including acestors
 
216
            at.
 
217
        :param fileobj: The file-like object to write to
 
218
        """
145
219
        write_op =  BundleWriteOperation(base, target, repository, fileobj)
146
220
        return write_op.do_write()
147
221
 
148
222
    def read(self, file):
 
223
        """return a reader object for a given file"""
149
224
        bundle = BundleInfoV4(file, self)
150
225
        return bundle
151
226
 
152
227
    @staticmethod
153
228
    def get_source_serializer(info):
 
229
        """Retrieve the serializer for a given info object"""
154
230
        return xml_serializer.format_registry.get(info['serializer'])
155
231
 
156
232
 
157
233
class BundleWriteOperation(object):
158
234
 
 
235
    """Perform the operation of writing revisions to a bundle"""
159
236
    @classmethod
160
237
    def from_old_args(cls, repository, revision_ids, forced_bases, fileobj):
161
238
        base, target = cls.get_base_target(revision_ids, forced_bases,
179
256
            self.revision_ids = revision_ids.difference(self.base_ancestry)
180
257
 
181
258
    def do_write(self):
 
259
        """Write all data to the bundle"""
182
260
        self.bundle.begin()
183
261
        self.write_info()
184
262
        self.write_files()
187
265
        return self.revision_ids
188
266
 
189
267
    def write_info(self):
 
268
        """Write format info"""
190
269
        serializer_format = self.repository.get_serializer_format()
191
270
        supports_rich_root = {True: 1, False: 0}[
192
271
            self.repository.supports_rich_root()]
194
273
                                    supports_rich_root=supports_rich_root)
195
274
 
196
275
    def iter_file_revisions(self):
197
 
        """This is the correct approach, but not compatible.
 
276
        """Iterate through all relevant revisions of all files.
198
277
 
199
 
        It does not work with bzr.dev, because certain old revisions were not
200
 
        converted correctly, and have the wrong "revision" marker in
201
 
        inventories.
 
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.
202
281
        """
203
282
        transaction = self.repository.get_transaction()
204
283
        altered = self.repository.fileids_altered_by_revision_ids(
208
287
            yield vf, file_id, file_revision_ids
209
288
 
210
289
    def iter_file_revisions_aggressive(self):
211
 
        """Ensure that all required revisions are fetched.
 
290
        """Iterate through all relevant revisions of all files.
212
291
 
213
292
        This uses the standard iter_file_revisions to determine what revisions
214
293
        are referred to by inventories, but then uses the versionedfile to
231
310
            yield vf, file_id, new_revision_ids
232
311
 
233
312
    def write_files(self):
 
313
        """Write bundle records for all revisions of all files"""
234
314
        for vf, file_id, revision_ids in self.iter_file_revisions_aggressive():
235
315
            self.add_mp_records('file', file_id, vf, revision_ids)
236
316
 
237
317
    def write_revisions(self):
 
318
        """Write bundle records for all revisions and signatures"""
238
319
        inv_vf = self.repository.get_inventory_weave()
239
320
        revision_order = list(multiparent.topo_iter(inv_vf, self.revision_ids))
240
321
        if self.target is not None and self.target in self.revision_ids:
268
349
        return base, target
269
350
 
270
351
    def add_mp_records(self, repo_kind, file_id, vf, revision_ids):
 
352
        """Add multi-parent diff records to a bundle"""
271
353
        revision_ids = list(multiparent.topo_iter(vf, revision_ids))
272
354
        mpdiffs = vf.make_mpdiffs(revision_ids)
273
355
        sha1s = vf.get_sha1s(revision_ids)
280
362
 
281
363
class BundleInfoV4(object):
282
364
 
 
365
    """Provide (most of) the BundleInfo interface"""
283
366
    def __init__(self, fileobj, serializer):
284
367
        self._fileobj = fileobj
285
368
        self._serializer = serializer