~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Aaron Bentley
  • Date: 2007-06-22 22:19:13 UTC
  • mto: (2520.5.2 bzr.mpbundle)
  • mto: This revision was merged to the branch mainline in revision 2631.
  • Revision ID: abentley@panoramicfeedback.com-20070622221913-mcjioqruw8rhgnd8
Improve locking in _BaseMergeDirective.from_object

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
17
from cStringIO import StringIO
 
18
import bz2
 
19
 
 
20
# The number of bytes per base64-encoded line.  We could use less, but it would
 
21
# be ugly
 
22
BASE64_LINE_BYTES = 57
 
23
 
 
24
from bzrlib import (
 
25
    diff,
 
26
    errors,
 
27
    iterablefile,
 
28
    multiparent,
 
29
    pack,
 
30
    revision as _mod_revision,
 
31
    trace,
 
32
    )
 
33
from bzrlib.bundle import bundle_data, serializer
 
34
from bzrlib.util import bencode
 
35
 
 
36
 
 
37
class BundleWriter(object):
 
38
 
 
39
    def __init__(self, fileobj):
 
40
        self._container = pack.ContainerWriter(self._write_encoded)
 
41
        self._fileobj = fileobj
 
42
        self._compressor = bz2.BZ2Compressor()
 
43
 
 
44
    def begin(self):
 
45
        self._fileobj.write(serializer._get_bundle_header('4alpha'))
 
46
        self._fileobj.write('#\n')
 
47
        self._container.begin()
 
48
 
 
49
    def _write_encoded(self, bytes):
 
50
        self._fileobj.write(self._compressor.compress(bytes))
 
51
 
 
52
    def end(self):
 
53
        self._container.end()
 
54
        self._fileobj.write(self._compressor.flush())
 
55
 
 
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',
 
60
                    'sha1': sha1}
 
61
        self._add_record(mp_bytes, metadata, repo_kind, revision_id, file_id)
 
62
 
 
63
    def add_fulltext_record(self, bytes, parents, repo_kind, revision_id,
 
64
                            file_id):
 
65
        self._add_record(bytes, {'parents': parents,
 
66
            'storage_kind': 'fulltext'}, repo_kind, revision_id, file_id)
 
67
 
 
68
    @staticmethod
 
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
 
73
        else:
 
74
            assert file_id is not None
 
75
        names = [content_kind, revision_id]
 
76
        if file_id is not None:
 
77
            names.append(file_id)
 
78
        return '/'.join(names)
 
79
 
 
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, [])
 
85
 
 
86
 
 
87
class BundleReader(object):
 
88
 
 
89
    def __init__(self, fileobj):
 
90
        line = fileobj.readline()
 
91
        if line != '\n':
 
92
            fileobj.readline()
 
93
        self.patch_lines = []
 
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)
 
98
 
 
99
    @staticmethod
 
100
    def iter_decode(fileobj):
 
101
        decompressor = bz2.BZ2Decompressor()
 
102
        for line in fileobj:
 
103
            yield decompressor.decompress()
 
104
 
 
105
    @staticmethod
 
106
    def decode_name(name):
 
107
        names = name.split('/')
 
108
        content_kind, revision_id = names[:2]
 
109
        if len(names) > 2:
 
110
            file_id = names[2]
 
111
        else:
 
112
            file_id = None
 
113
        return content_kind, revision_id, file_id
 
114
 
 
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)
 
121
 
 
122
 
 
123
class BundleSerializerV4(serializer.BundleSerializer):
 
124
 
 
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()
 
129
 
 
130
    def write_bundle(self, repository, target, base, fileobj):
 
131
        write_op =  BundleWriteOperation(base, target, repository, fileobj)
 
132
        return write_op.do_write()
 
133
 
 
134
    def read(self, file):
 
135
        bundle = BundleInfoV4(file, self)
 
136
        return bundle
 
137
 
 
138
 
 
139
class BundleWriteOperation(object):
 
140
 
 
141
    @classmethod
 
142
    def from_old_args(cls, repository, revision_ids, forced_bases, fileobj):
 
143
        base, target = cls.get_base_target(revision_ids, forced_bases,
 
144
                                           repository)
 
145
        return BundleWriteOperation(base, target, repository, fileobj,
 
146
                                    revision_ids)
 
147
 
 
148
    def __init__(self, base, target, repository, fileobj, revision_ids=None):
 
149
        self.base = base
 
150
        self.target = target
 
151
        self.repository = repository
 
152
        bundle = BundleWriter(fileobj)
 
153
        self.bundle = bundle
 
154
        self.base_ancestry = set(repository.get_ancestry(base,
 
155
                                                         topo_sorted=False))
 
156
        if revision_ids is not None:
 
157
            self.revision_ids = revision_ids
 
158
        else:
 
159
            revision_ids = set(repository.get_ancestry(target,
 
160
                                                       topo_sorted=False))
 
161
            self.revision_ids = revision_ids.difference(self.base_ancestry)
 
162
 
 
163
    def do_write(self):
 
164
        self.bundle.begin()
 
165
        self.write_files()
 
166
        self.write_revisions()
 
167
        self.bundle.end()
 
168
        return self.revision_ids
 
169
 
 
170
    def iter_file_revisions(self):
 
171
        """This is the correct approach, but not compatible.
 
172
 
 
173
        It does not work with bzr.dev, because certain old revisions were not
 
174
        converted correctly, and have the wrong "revision" marker in
 
175
        inventories.
 
176
        """
 
177
        transaction = self.repository.get_transaction()
 
178
        altered = self.repository.fileids_altered_by_revision_ids(
 
179
            self.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
 
183
 
 
184
    def iter_file_revisions_aggressive(self):
 
185
        """Ensure that all required revisions are fetched.
 
186
 
 
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.
 
190
 
 
191
        All build dependencies which are not ancestors of the base revision
 
192
        are emitted.
 
193
        """
 
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:
 
200
                    continue
 
201
                if revision_id in self.base_ancestry:
 
202
                    continue
 
203
                new_revision_ids.add(revision_id)
 
204
                pending.extend(vf.get_parents(revision_id))
 
205
            yield vf, file_id, new_revision_ids
 
206
 
 
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)
 
210
 
 
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)
 
223
            try:
 
224
                self.bundle.add_fulltext_record(
 
225
                    self.repository.get_signature_text(
 
226
                    revision_id), parents, 'signature', revision_id, None)
 
227
            except errors.NoSuchRevision:
 
228
                pass
 
229
 
 
230
    @staticmethod
 
231
    def get_base_target(revision_ids, forced_bases, repository):
 
232
        if len(revision_ids) == 0:
 
233
            return None, None
 
234
        target = revision_ids[0]
 
235
        base = forced_bases.get(target)
 
236
        if base is None:
 
237
            parents = repository.get_revision(target).parent_ids
 
238
            if len(parents) == 0:
 
239
                base = _mod_revision.NULL_REVISION
 
240
            else:
 
241
                base = parents[0]
 
242
        return base, target
 
243
 
 
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)
 
253
 
 
254
 
 
255
class BundleInfoV4(object):
 
256
 
 
257
    def __init__(self, fileobj, serializer):
 
258
        self._fileobj = fileobj
 
259
        self._serializer = serializer
 
260
        self.__real_revisions = None
 
261
        self.__revisions = None
 
262
 
 
263
    def install(self, repository):
 
264
        return self.install_revisions(repository)
 
265
 
 
266
    def install_revisions(self, repository):
 
267
        repository.lock_write()
 
268
        try:
 
269
            ri = RevisionInstaller(self.get_bundle_reader(),
 
270
                                   self._serializer, repository)
 
271
            return ri.install()
 
272
        finally:
 
273
            repository.unlock()
 
274
 
 
275
    def get_bundle_reader(self):
 
276
        self._fileobj.seek(0)
 
277
        return BundleReader(self._fileobj)
 
278
 
 
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)
 
291
 
 
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
 
299
 
 
300
    revisions = property(_get_revisions)
 
301
 
 
302
    def _get_target(self):
 
303
        return self.revisions[-1].revision_id
 
304
 
 
305
    target = property(_get_target)
 
306
 
 
307
 
 
308
class RevisionInstaller(object):
 
309
 
 
310
    def __init__(self, container, serializer, repository):
 
311
        self._container = container
 
312
        self._serializer = serializer
 
313
        self._repository = repository
 
314
 
 
315
    def install(self):
 
316
        current_file = None
 
317
        current_versionedfile = None
 
318
        pending_file_records = []
 
319
        added_inv = set()
 
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)
 
326
                current_file = None
 
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:
 
346
                    continue
 
347
                pending_file_records.append((revision_id, metadata, bytes))
 
348
        self._install_mp_records(current_versionedfile, pending_file_records)
 
349
        return target_revision
 
350
 
 
351
    def _install_mp_records(self, versionedfile, records):
 
352
        if len(records) == 0:
 
353
            return
 
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)
 
358
 
 
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)])
 
362
 
 
363
    def _install_revision(self, revision_id, metadata, text):
 
364
        if self._repository.has_revision(revision_id):
 
365
            return
 
366
        self._repository._add_revision_text(revision_id, text)
 
367
 
 
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())