1
# Copyright (C) 2007 Canonical Ltd
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.
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.
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
17
from cStringIO import StringIO
20
# The number of bytes per base64-encoded line. We could use less, but it would
22
BASE64_LINE_BYTES = 57
30
revision as _mod_revision,
33
from bzrlib.bundle import bundle_data, serializer
34
from bzrlib.util import bencode
37
class BundleWriter(object):
39
def __init__(self, fileobj):
40
self._container = pack.ContainerWriter(self._write_encoded)
41
self._fileobj = fileobj
42
self._compressor = bz2.BZ2Compressor()
45
self._fileobj.write(serializer._get_bundle_header('4alpha'))
46
self._fileobj.write('#\n')
47
self._container.begin()
49
def _write_encoded(self, bytes):
50
self._fileobj.write(self._compressor.compress(bytes))
54
self._fileobj.write(self._compressor.flush())
56
def add_multiparent_record(self, mp_bytes, sha1, parents, repo_kind,
57
revision_id, file_id):
58
metadata = {'parents': parents,
59
'storage_kind': 'mpdiff',
61
self._add_record(mp_bytes, metadata, repo_kind, revision_id, file_id)
63
def add_fulltext_record(self, bytes, parents, repo_kind, revision_id,
65
self._add_record(bytes, {'parents': parents,
66
'storage_kind': 'fulltext'}, repo_kind, revision_id, file_id)
69
def encode_name(content_kind, revision_id, file_id=None):
70
assert content_kind in ('revision', 'file', 'inventory', 'signature')
71
if content_kind in ('revision', 'inventory', 'signature'):
72
assert file_id is None
74
assert file_id is not None
75
names = [content_kind, revision_id]
76
if file_id is not None:
78
return '/'.join(names)
80
def _add_record(self, bytes, metadata, repo_kind, revision_id, file_id):
81
name = self.encode_name(repo_kind, revision_id, file_id)
82
metadata = bencode.bencode(metadata)
83
self._container.add_bytes_record(metadata, [name])
84
self._container.add_bytes_record(bytes, [])
87
class BundleReader(object):
89
def __init__(self, fileobj):
90
line = fileobj.readline()
94
self._container = pack.ContainerReader(
95
StringIO(fileobj.read().decode('bz2')).read)
96
# Have to use StringIO for perf, until ContainerReader fixed.
97
# iterablefile.IterableFile(self.iter_decode(fileobj)).read)
100
def iter_decode(fileobj):
101
decompressor = bz2.BZ2Decompressor()
103
yield decompressor.decompress()
106
def decode_name(name):
107
names = name.split('/')
108
content_kind, revision_id = names[:2]
113
return content_kind, revision_id, file_id
115
def iter_records(self):
116
iterator = self._container.iter_records()
117
for (name,), meta_bytes in iterator:
118
metadata = bencode.bdecode(meta_bytes(None))
119
_unused, bytes = iterator.next()
120
yield (bytes(None), metadata) + self.decode_name(name)
123
class BundleSerializerV4(serializer.BundleSerializer):
125
def write(self, repository, revision_ids, forced_bases, fileobj):
126
write_op = BundleWriteOperation.from_old_args(repository, revision_ids,
127
forced_bases, fileobj)
128
return write_op.do_write()
130
def write_bundle(self, repository, target, base, fileobj):
131
write_op = BundleWriteOperation(base, target, repository, fileobj)
132
return write_op.do_write()
134
def read(self, file):
135
bundle = BundleInfoV4(file, self)
139
class BundleWriteOperation(object):
142
def from_old_args(cls, repository, revision_ids, forced_bases, fileobj):
143
base, target = cls.get_base_target(revision_ids, forced_bases,
145
return BundleWriteOperation(base, target, repository, fileobj,
148
def __init__(self, base, target, repository, fileobj, revision_ids=None):
151
self.repository = repository
152
bundle = BundleWriter(fileobj)
154
self.base_ancestry = set(repository.get_ancestry(base,
156
if revision_ids is not None:
157
self.revision_ids = revision_ids
159
revision_ids = set(repository.get_ancestry(target,
161
self.revision_ids = revision_ids.difference(self.base_ancestry)
166
self.write_revisions()
168
return self.revision_ids
170
def iter_file_revisions(self):
171
"""This is the correct approach, but not compatible.
173
It does not work with bzr.dev, because certain old revisions were not
174
converted correctly, and have the wrong "revision" marker in
177
transaction = self.repository.get_transaction()
178
altered = self.repository.fileids_altered_by_revision_ids(
180
for file_id, file_revision_ids in altered.iteritems():
181
vf = self.repository.weave_store.get_weave(file_id, transaction)
182
yield vf, file_id, file_revision_ids
184
def iter_file_revisions_aggressive(self):
185
"""Ensure that all required revisions are fetched.
187
This uses the standard iter_file_revisions to determine what revisions
188
are referred to by inventories, but then uses the versionedfile to
189
determine what the build-dependencies of each required revision.
191
All build dependencies which are not ancestors of the base revision
194
for vf, file_id, file_revision_ids in self.iter_file_revisions():
195
new_revision_ids = set()
196
pending = list(file_revision_ids)
197
while len(pending) > 0:
198
revision_id = pending.pop()
199
if revision_id in new_revision_ids:
201
if revision_id in self.base_ancestry:
203
new_revision_ids.add(revision_id)
204
pending.extend(vf.get_parents(revision_id))
205
yield vf, file_id, new_revision_ids
207
def write_files(self):
208
for vf, file_id, revision_ids in self.iter_file_revisions_aggressive():
209
self.add_mp_records('file', file_id, vf, revision_ids)
211
def write_revisions(self):
212
inv_vf = self.repository.get_inventory_weave()
213
revision_order = list(multiparent.topo_iter(inv_vf, self.revision_ids))
214
if self.target is not None and self.target in self.revision_ids:
215
revision_order.remove(self.target)
216
revision_order.append(self.target)
217
self.add_mp_records('inventory', None, inv_vf, revision_order)
218
for revision_id in revision_order:
219
parents = self.repository.revision_parents(revision_id)
220
revision_text = self.repository.get_revision_xml(revision_id)
221
self.bundle.add_fulltext_record(revision_text, parents,
222
'revision', revision_id, None)
224
self.bundle.add_fulltext_record(
225
self.repository.get_signature_text(
226
revision_id), parents, 'signature', revision_id, None)
227
except errors.NoSuchRevision:
231
def get_base_target(revision_ids, forced_bases, repository):
232
if len(revision_ids) == 0:
234
target = revision_ids[0]
235
base = forced_bases.get(target)
237
parents = repository.get_revision(target).parent_ids
238
if len(parents) == 0:
239
base = _mod_revision.NULL_REVISION
244
def add_mp_records(self, repo_kind, file_id, vf, revision_ids):
245
revision_ids = list(multiparent.topo_iter(vf, revision_ids))
246
mpdiffs = vf.make_mpdiffs(revision_ids)
247
for mpdiff, revision_id in zip(mpdiffs, revision_ids):
248
parents = vf.get_parents(revision_id)
249
sha1 = vf.get_sha1(revision_id)
250
text = ''.join(mpdiff.to_patch())
251
self.bundle.add_multiparent_record(text, sha1, parents, repo_kind,
252
revision_id, file_id)
255
class BundleInfoV4(object):
257
def __init__(self, fileobj, serializer):
258
self._fileobj = fileobj
259
self._serializer = serializer
260
self.__real_revisions = None
261
self.__revisions = None
263
def install(self, repository):
264
return self.install_revisions(repository)
266
def install_revisions(self, repository):
267
repository.lock_write()
269
ri = RevisionInstaller(self.get_bundle_reader(),
270
self._serializer, repository)
275
def get_bundle_reader(self):
276
self._fileobj.seek(0)
277
return BundleReader(self._fileobj)
279
def _get_real_revisions(self):
280
from bzrlib import xml7
281
if self.__real_revisions is None:
282
self.__real_revisions = []
283
bundle_reader = self.get_bundle_reader()
284
for bytes, parents, repo_kind, revision_id, file_id in \
285
bundle_reader.iter_records():
286
if repo_kind == 'revision':
287
rev = xml7.serializer_v7.read_revision_from_string(bytes)
288
self.__real_revisions.append(rev)
289
return self.__real_revisions
290
real_revisions = property(_get_real_revisions)
292
def _get_revisions(self):
293
if self.__revisions is None:
294
self.__revisions = []
295
for revision in self.real_revisions:
296
self.__revisions.append(
297
bundle_data.RevisionInfo.from_revision(revision))
298
return self.__revisions
300
revisions = property(_get_revisions)
302
def _get_target(self):
303
return self.revisions[-1].revision_id
305
target = property(_get_target)
308
class RevisionInstaller(object):
310
def __init__(self, container, serializer, repository):
311
self._container = container
312
self._serializer = serializer
313
self._repository = repository
317
current_versionedfile = None
318
pending_file_records = []
320
target_revision = None
321
for bytes, metadata, repo_kind, revision_id, file_id in\
322
self._container.iter_records():
323
if repo_kind != 'file':
324
self._install_mp_records(current_versionedfile,
325
pending_file_records)
327
current_versionedfile = None
328
pending_file_records = []
329
if repo_kind == 'inventory':
330
self._install_inventory(revision_id, metadata, bytes)
331
if repo_kind == 'revision':
332
target_revision = revision_id
333
self._install_revision(revision_id, metadata, bytes)
334
if repo_kind == 'signature':
335
self._install_signature(revision_id, metadata, bytes)
336
if repo_kind == 'file':
337
if file_id != current_file:
338
self._install_mp_records(current_versionedfile,
339
pending_file_records)
340
current_file = file_id
341
current_versionedfile = \
342
self._repository.weave_store.get_weave_or_empty(
343
file_id, self._repository.get_transaction())
344
pending_file_records = []
345
if revision_id in current_versionedfile:
347
pending_file_records.append((revision_id, metadata, bytes))
348
self._install_mp_records(current_versionedfile, pending_file_records)
349
return target_revision
351
def _install_mp_records(self, versionedfile, records):
352
if len(records) == 0:
354
d_func = multiparent.MultiParent.from_patch
355
vf_records = [(r, m['parents'], m['sha1'], d_func(t)) for r, m, t in
356
records if r not in versionedfile]
357
versionedfile.add_mpdiffs(vf_records)
359
def _install_inventory(self, revision_id, metadata, text):
360
vf = self._repository.get_inventory_weave()
361
return self._install_mp_records(vf, [(revision_id, metadata, text)])
363
def _install_revision(self, revision_id, metadata, text):
364
if self._repository.has_revision(revision_id):
366
self._repository._add_revision_text(revision_id, text)
368
def _install_signature(self, revision_id, metadata, text):
369
self._repository._revision_store.add_revision_signature_text(
370
revision_id, text, self._repository.get_transaction())