35
35
class BundleWriter(object):
37
"""Writer for bundle-format files.
39
This serves roughly the same purpose as ContainerReader, but acts as a
42
Provides ways of writing the spcific record types supported this bundle
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()
50
def _write_encoded(self, bytes):
51
"""Write bzip2-encoded bytes to the file"""
52
self._fileobj.write(self._compressor.compress(bytes))
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()
47
def _write_encoded(self, bytes):
48
self._fileobj.write(self._compressor.compress(bytes))
61
"""Finish writing the bundle"""
51
62
self._container.end()
52
63
self._fileobj.write(self._compressor.flush())
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
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
73
:revision_id: The revision id of the mpdiff being added.
74
:file_id: The file-id of the file, or None for inventories.
56
76
metadata = {'parents': parents,
57
77
'storage_kind': 'mpdiff',
61
81
def add_fulltext_record(self, bytes, parents, repo_kind, revision_id,
83
"""Add a record for a fulltext
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
89
:revision_id: The revision id of the fulltext being added.
90
:file_id: must be None
92
metadata = {'parents': parents,
93
'storage_kind': 'mpdiff',
63
95
self._add_record(bytes, {'parents': parents,
64
96
'storage_kind': 'fulltext'}, repo_kind, revision_id, file_id)
66
98
def add_info_record(self, **kwargs):
99
"""Add an info record to the bundle
101
Any parameters may be supplied, except 'self' and 'storage_kind'.
102
Values must be lists, strings, integers, dicts, or a combination.
67
104
kwargs['storage_kind'] = 'header'
68
105
self._add_record(None, kwargs, 'info', None, None)
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',
74
if content_kind in ('revision', 'inventory', 'signature', 'info'):
113
if content_kind == 'file':
114
assert file_id is not None
75
116
assert file_id is None
77
assert file_id is not None
78
117
if content_kind == 'info':
79
118
assert revision_id is None
87
126
return '/'.join(names)
89
128
def _add_record(self, bytes, metadata, repo_kind, revision_id, file_id):
129
"""Add a bundle record to the container.
131
Most bundle records are recorded as header/body pairs, with the
132
body being nameless. Records with storage_kind 'header' have no
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])
97
142
class BundleReader(object):
144
"""Reader for bundle-format files.
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
99
150
def __init__(self, fileobj):
100
151
line = fileobj.readline()
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)
114
166
def decode_name(name):
167
"""Decode a name from its container form into a semantic form
169
:retval: content_kind, revision_id, file_id
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
125
181
def iter_records(self):
182
"""Iterate through bundle records
184
:return: a generator of (bytes, metadata, content_kind, revision_id,
126
187
iterator = self._container.iter_records()
127
188
for (name,), meta_bytes in iterator:
128
189
metadata = bencode.bdecode(meta_bytes(None))
137
198
class BundleSerializerV4(serializer.BundleSerializer):
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
204
For backwards-compatibility only
140
206
write_op = BundleWriteOperation.from_old_args(repository, revision_ids,
141
207
forced_bases, fileobj)
142
208
return write_op.do_write()
144
210
def write_bundle(self, repository, target, base, fileobj):
211
"""Write a bundle to a file object
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
217
:param fileobj: The file-like object to write to
145
219
write_op = BundleWriteOperation(base, target, repository, fileobj)
146
220
return write_op.do_write()
148
222
def read(self, file):
223
"""return a reader object for a given file"""
149
224
bundle = BundleInfoV4(file, self)
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'])
157
233
class BundleWriteOperation(object):
235
"""Perform the operation of writing revisions to a bundle"""
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,
194
273
supports_rich_root=supports_rich_root)
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.
199
It does not work with bzr.dev, because certain old revisions were not
200
converted correctly, and have the wrong "revision" marker in
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.
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
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.
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
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)
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
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)
281
363
class BundleInfoV4(object):
365
"""Provide (most of) the BundleInfo interface"""
283
366
def __init__(self, fileobj, serializer):
284
367
self._fileobj = fileobj
285
368
self._serializer = serializer