~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Andrew Bennetts
  • Date: 2010-10-08 08:15:14 UTC
  • mto: This revision was merged to the branch mainline in revision 5498.
  • Revision ID: andrew.bennetts@canonical.com-20101008081514-dviqzrdfwyzsqbz2
Split NEWS into per-release doc/en/release-notes/bzr-*.txt

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007 Canonical Ltd
 
1
# Copyright (C) 2007-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
from cStringIO import StringIO
18
18
import bz2
22
22
    diff,
23
23
    errors,
24
24
    iterablefile,
 
25
    lru_cache,
25
26
    multiparent,
26
27
    osutils,
27
28
    pack,
28
29
    revision as _mod_revision,
 
30
    serializer,
29
31
    trace,
30
 
    xml_serializer,
 
32
    ui,
 
33
    versionedfile as _mod_versionedfile,
31
34
    )
32
 
from bzrlib.bundle import bundle_data, serializer
33
 
from bzrlib.util import bencode
 
35
from bzrlib.bundle import bundle_data, serializer as bundle_serializer
 
36
from bzrlib import bencode
 
37
 
 
38
 
 
39
class _MPDiffInventoryGenerator(_mod_versionedfile._MPDiffGenerator):
 
40
    """Generate Inventory diffs serialized inventories."""
 
41
 
 
42
    def __init__(self, repo, inventory_keys):
 
43
        super(_MPDiffInventoryGenerator, self).__init__(repo.inventories,
 
44
            inventory_keys)
 
45
        self.repo = repo
 
46
        self.sha1s = {}
 
47
 
 
48
    def iter_diffs(self):
 
49
        """Compute the diffs one at a time."""
 
50
        # This is instead of compute_diffs() since we guarantee our ordering of
 
51
        # inventories, we don't have to do any buffering
 
52
        self._find_needed_keys()
 
53
        # We actually use a slightly different ordering. We grab all of the
 
54
        # parents first, and then grab the ordered requests.
 
55
        needed_ids = [k[-1] for k in self.present_parents]
 
56
        needed_ids.extend([k[-1] for k in self.ordered_keys])
 
57
        inv_to_str = self.repo._serializer.write_inventory_to_string
 
58
        for inv in self.repo.iter_inventories(needed_ids):
 
59
            revision_id = inv.revision_id
 
60
            key = (revision_id,)
 
61
            if key in self.present_parents:
 
62
                # Not a key we will transmit, which is a shame, since because
 
63
                # of that bundles don't work with stacked branches
 
64
                parent_ids = None
 
65
            else:
 
66
                parent_ids = [k[-1] for k in self.parent_map[key]]
 
67
            as_bytes = inv_to_str(inv)
 
68
            self._process_one_record(key, (as_bytes,))
 
69
            if parent_ids is None:
 
70
                continue
 
71
            diff = self.diffs.pop(key)
 
72
            sha1 = osutils.sha_string(as_bytes)
 
73
            yield revision_id, parent_ids, sha1, diff
34
74
 
35
75
 
36
76
class BundleWriter(object):
54
94
 
55
95
    def begin(self):
56
96
        """Start writing the bundle"""
57
 
        self._fileobj.write(serializer._get_bundle_header(
58
 
            serializer.v4_string))
 
97
        self._fileobj.write(bundle_serializer._get_bundle_header(
 
98
            bundle_serializer.v4_string))
59
99
        self._fileobj.write('#\n')
60
100
        self._container.begin()
61
101
 
107
147
    @staticmethod
108
148
    def encode_name(content_kind, revision_id, file_id=None):
109
149
        """Encode semantic ids as a container name"""
110
 
        assert content_kind in ('revision', 'file', 'inventory', 'signature',
111
 
                                'info')
112
 
 
 
150
        if content_kind not in ('revision', 'file', 'inventory', 'signature',
 
151
                'info'):
 
152
            raise ValueError(content_kind)
113
153
        if content_kind == 'file':
114
 
            assert file_id is not None
 
154
            if file_id is None:
 
155
                raise AssertionError()
115
156
        else:
116
 
            assert file_id is None
 
157
            if file_id is not None:
 
158
                raise AssertionError()
117
159
        if content_kind == 'info':
118
 
            assert revision_id is None
119
 
        else:
120
 
            assert revision_id is not None
 
160
            if revision_id is not None:
 
161
                raise AssertionError()
 
162
        elif revision_id is None:
 
163
            raise AssertionError()
121
164
        names = [n.replace('/', '//') for n in
122
165
                 (content_kind, revision_id, file_id) if n is not None]
123
166
        return '/'.join(names)
160
203
            source_file = iterablefile.IterableFile(self.iter_decode(fileobj))
161
204
        else:
162
205
            source_file = StringIO(bz2.decompress(fileobj.read()))
163
 
        self._container = pack.ContainerReader(source_file)
 
206
        self._container_file = source_file
164
207
 
165
208
    @staticmethod
166
209
    def iter_decode(fileobj):
167
210
        """Iterate through decoded fragments of the file"""
168
211
        decompressor = bz2.BZ2Decompressor()
169
212
        for line in fileobj:
170
 
            yield decompressor.decompress(line)
 
213
            try:
 
214
                yield decompressor.decompress(line)
 
215
            except EOFError:
 
216
                return
171
217
 
172
218
    @staticmethod
173
219
    def decode_name(name):
199
245
        :return: a generator of (bytes, metadata, content_kind, revision_id,
200
246
            file_id)
201
247
        """
202
 
        iterator = self._container.iter_records()
203
 
        for names, meta_bytes in iterator:
 
248
        iterator = pack.iter_records_from_file(self._container_file)
 
249
        for names, bytes in iterator:
204
250
            if len(names) != 1:
205
251
                raise errors.BadBundle('Record has %d names instead of 1'
206
252
                                       % len(names))
207
 
            metadata = bencode.bdecode(meta_bytes(None))
 
253
            metadata = bencode.bdecode(bytes)
208
254
            if metadata['storage_kind'] == 'header':
209
255
                bytes = None
210
256
            else:
211
257
                _unused, bytes = iterator.next()
212
 
                bytes = bytes(None)
213
258
            yield (bytes, metadata) + self.decode_name(names[0][0])
214
259
 
215
260
 
216
 
class BundleSerializerV4(serializer.BundleSerializer):
 
261
class BundleSerializerV4(bundle_serializer.BundleSerializer):
217
262
    """Implement the high-level bundle interface"""
218
263
 
219
264
    def write(self, repository, revision_ids, forced_bases, fileobj):
245
290
    @staticmethod
246
291
    def get_source_serializer(info):
247
292
        """Retrieve the serializer for a given info object"""
248
 
        return xml_serializer.format_registry.get(info['serializer'])
 
293
        return serializer.format_registry.get(info['serializer'])
249
294
 
250
295
 
251
296
class BundleWriteOperation(object):
265
310
        self.repository = repository
266
311
        bundle = BundleWriter(fileobj)
267
312
        self.bundle = bundle
268
 
        self.base_ancestry = set(repository.get_ancestry(base,
269
 
                                                         topo_sorted=False))
270
313
        if revision_ids is not None:
271
314
            self.revision_ids = revision_ids
272
315
        else:
273
 
            revision_ids = set(repository.get_ancestry(target,
274
 
                                                       topo_sorted=False))
275
 
            self.revision_ids = revision_ids.difference(self.base_ancestry)
 
316
            graph = repository.get_graph()
 
317
            revision_ids = graph.find_unique_ancestors(target, [base])
 
318
            # Strip ghosts
 
319
            parents = graph.get_parent_map(revision_ids)
 
320
            self.revision_ids = [r for r in revision_ids if r in parents]
 
321
        self.revision_keys = set([(revid,) for revid in self.revision_ids])
276
322
 
277
323
    def do_write(self):
278
324
        """Write all data to the bundle"""
279
 
        self.bundle.begin()
280
 
        self.write_info()
281
 
        self.write_files()
282
 
        self.write_revisions()
283
 
        self.bundle.end()
 
325
        trace.note('Bundling %d revision(s).', len(self.revision_ids))
 
326
        self.repository.lock_read()
 
327
        try:
 
328
            self.bundle.begin()
 
329
            self.write_info()
 
330
            self.write_files()
 
331
            self.write_revisions()
 
332
            self.bundle.end()
 
333
        finally:
 
334
            self.repository.unlock()
284
335
        return self.revision_ids
285
336
 
286
337
    def write_info(self):
291
342
        self.bundle.add_info_record(serializer=serializer_format,
292
343
                                    supports_rich_root=supports_rich_root)
293
344
 
294
 
    def iter_file_revisions(self):
295
 
        """Iterate through all relevant revisions of all files.
296
 
 
297
 
        This is the correct implementation, but is not compatible with bzr.dev,
298
 
        because certain old revisions were not converted correctly, and have
299
 
        the wrong "revision" marker in inventories.
300
 
        """
301
 
        transaction = self.repository.get_transaction()
302
 
        altered = self.repository.fileids_altered_by_revision_ids(
303
 
            self.revision_ids)
304
 
        for file_id, file_revision_ids in altered.iteritems():
305
 
            vf = self.repository.weave_store.get_weave(file_id, transaction)
306
 
            yield vf, file_id, file_revision_ids
307
 
 
308
 
    def iter_file_revisions_aggressive(self):
309
 
        """Iterate through all relevant revisions of all files.
310
 
 
311
 
        This uses the standard iter_file_revisions to determine what revisions
312
 
        are referred to by inventories, but then uses the versionedfile to
313
 
        determine what the build-dependencies of each required revision.
314
 
 
315
 
        All build dependencies which are not ancestors of the base revision
316
 
        are emitted.
317
 
        """
318
 
        for vf, file_id, file_revision_ids in self.iter_file_revisions():
319
 
            new_revision_ids = set()
320
 
            pending = list(file_revision_ids)
321
 
            while len(pending) > 0:
322
 
                revision_id = pending.pop()
323
 
                if revision_id in new_revision_ids:
324
 
                    continue
325
 
                if revision_id in self.base_ancestry:
326
 
                    continue
327
 
                new_revision_ids.add(revision_id)
328
 
                pending.extend(vf.get_parents(revision_id))
329
 
            yield vf, file_id, new_revision_ids
330
 
 
331
345
    def write_files(self):
332
346
        """Write bundle records for all revisions of all files"""
333
 
        for vf, file_id, revision_ids in self.iter_file_revisions_aggressive():
334
 
            self.add_mp_records('file', file_id, vf, revision_ids)
 
347
        text_keys = []
 
348
        altered_fileids = self.repository.fileids_altered_by_revision_ids(
 
349
                self.revision_ids)
 
350
        for file_id, revision_ids in altered_fileids.iteritems():
 
351
            for revision_id in revision_ids:
 
352
                text_keys.append((file_id, revision_id))
 
353
        self._add_mp_records_keys('file', self.repository.texts, text_keys)
335
354
 
336
355
    def write_revisions(self):
337
356
        """Write bundle records for all revisions and signatures"""
338
 
        inv_vf = self.repository.get_inventory_weave()
339
 
        revision_order = list(multiparent.topo_iter(inv_vf, self.revision_ids))
 
357
        inv_vf = self.repository.inventories
 
358
        topological_order = [key[-1] for key in multiparent.topo_iter_keys(
 
359
                                inv_vf, self.revision_keys)]
 
360
        revision_order = topological_order
340
361
        if self.target is not None and self.target in self.revision_ids:
 
362
            # Make sure the target revision is always the last entry
 
363
            revision_order = list(topological_order)
341
364
            revision_order.remove(self.target)
342
365
            revision_order.append(self.target)
343
 
        self.add_mp_records('inventory', None, inv_vf, revision_order)
344
 
        parents_list = self.repository.get_parents(revision_order)
345
 
        for parents, revision_id in zip(parents_list, revision_order):
346
 
            revision_text = self.repository.get_revision_xml(revision_id)
 
366
        if self.repository._serializer.support_altered_by_hack:
 
367
            # Repositories that support_altered_by_hack means that
 
368
            # inventories.make_mpdiffs() contains all the data about the tree
 
369
            # shape. Formats without support_altered_by_hack require
 
370
            # chk_bytes/etc, so we use a different code path.
 
371
            self._add_mp_records_keys('inventory', inv_vf,
 
372
                                      [(revid,) for revid in topological_order])
 
373
        else:
 
374
            # Inventories should always be added in pure-topological order, so
 
375
            # that we can apply the mpdiff for the child to the parent texts.
 
376
            self._add_inventory_mpdiffs_from_serializer(topological_order)
 
377
        self._add_revision_texts(revision_order)
 
378
 
 
379
    def _add_inventory_mpdiffs_from_serializer(self, revision_order):
 
380
        """Generate mpdiffs by serializing inventories.
 
381
 
 
382
        The current repository only has part of the tree shape information in
 
383
        the 'inventories' vf. So we use serializer.write_inventory_to_string to
 
384
        get a 'full' representation of the tree shape, and then generate
 
385
        mpdiffs on that data stream. This stream can then be reconstructed on
 
386
        the other side.
 
387
        """
 
388
        inventory_key_order = [(r,) for r in revision_order]
 
389
        generator = _MPDiffInventoryGenerator(self.repository,
 
390
                                              inventory_key_order)
 
391
        for revision_id, parent_ids, sha1, diff in generator.iter_diffs():
 
392
            text = ''.join(diff.to_patch())
 
393
            self.bundle.add_multiparent_record(text, sha1, parent_ids,
 
394
                                               'inventory', revision_id, None)
 
395
 
 
396
    def _add_revision_texts(self, revision_order):
 
397
        parent_map = self.repository.get_parent_map(revision_order)
 
398
        revision_to_str = self.repository._serializer.write_revision_to_string
 
399
        revisions = self.repository.get_revisions(revision_order)
 
400
        for revision in revisions:
 
401
            revision_id = revision.revision_id
 
402
            parents = parent_map.get(revision_id, None)
 
403
            revision_text = revision_to_str(revision)
347
404
            self.bundle.add_fulltext_record(revision_text, parents,
348
405
                                       'revision', revision_id)
349
406
            try:
368
425
                base = parents[0]
369
426
        return base, target
370
427
 
371
 
    def add_mp_records(self, repo_kind, file_id, vf, revision_ids):
 
428
    def _add_mp_records_keys(self, repo_kind, vf, keys):
372
429
        """Add multi-parent diff records to a bundle"""
373
 
        revision_ids = list(multiparent.topo_iter(vf, revision_ids))
374
 
        mpdiffs = vf.make_mpdiffs(revision_ids)
375
 
        sha1s = vf.get_sha1s(revision_ids)
376
 
        for mpdiff, revision_id, sha1, in zip(mpdiffs, revision_ids, sha1s):
377
 
            parents = vf.get_parents(revision_id)
 
430
        ordered_keys = list(multiparent.topo_iter_keys(vf, keys))
 
431
        mpdiffs = vf.make_mpdiffs(ordered_keys)
 
432
        sha1s = vf.get_sha1s(ordered_keys)
 
433
        parent_map = vf.get_parent_map(ordered_keys)
 
434
        for mpdiff, item_key, in zip(mpdiffs, ordered_keys):
 
435
            sha1 = sha1s[item_key]
 
436
            parents = [key[-1] for key in parent_map[item_key]]
378
437
            text = ''.join(mpdiff.to_patch())
 
438
            # Infer file id records as appropriate.
 
439
            if len(item_key) == 2:
 
440
                file_id = item_key[0]
 
441
            else:
 
442
                file_id = None
379
443
            self.bundle.add_multiparent_record(text, sha1, parents, repo_kind,
380
 
                                               revision_id, file_id)
 
444
                                               item_key[-1], file_id)
381
445
 
382
446
 
383
447
class BundleInfoV4(object):
466
530
        self._info = None
467
531
 
468
532
    def install(self):
469
 
        """Perform the installation"""
 
533
        """Perform the installation.
 
534
 
 
535
        Must be called with the Repository locked.
 
536
        """
 
537
        self._repository.start_write_group()
 
538
        try:
 
539
            result = self._install_in_write_group()
 
540
        except:
 
541
            self._repository.abort_write_group()
 
542
            raise
 
543
        self._repository.commit_write_group()
 
544
        return result
 
545
 
 
546
    def _install_in_write_group(self):
470
547
        current_file = None
471
548
        current_versionedfile = None
472
549
        pending_file_records = []
477
554
        for bytes, metadata, repo_kind, revision_id, file_id in\
478
555
            self._container.iter_records():
479
556
            if repo_kind == 'info':
480
 
                assert self._info is None
 
557
                if self._info is not None:
 
558
                    raise AssertionError()
481
559
                self._handle_info(metadata)
482
 
            if (repo_kind, file_id) != ('file', current_file):
483
 
                if len(pending_file_records) > 0:
484
 
                    self._install_mp_records(current_versionedfile,
485
 
                                             pending_file_records)
 
560
            if (pending_file_records and
 
561
                (repo_kind, file_id) != ('file', current_file)):
 
562
                # Flush the data for a single file - prevents memory
 
563
                # spiking due to buffering all files in memory.
 
564
                self._install_mp_records_keys(self._repository.texts,
 
565
                    pending_file_records)
486
566
                current_file = None
487
 
                current_versionedfile = None
488
 
                pending_file_records = []
 
567
                del pending_file_records[:]
489
568
            if len(pending_inventory_records) > 0 and repo_kind != 'inventory':
490
 
                self._install_inventory_records(inventory_vf,
491
 
                                                pending_inventory_records)
 
569
                self._install_inventory_records(pending_inventory_records)
492
570
                pending_inventory_records = []
493
571
            if repo_kind == 'inventory':
494
 
                if inventory_vf is None:
495
 
                    inventory_vf = self._repository.get_inventory_weave()
496
 
                if revision_id not in inventory_vf:
497
 
                    pending_inventory_records.append((revision_id, metadata,
498
 
                                                      bytes))
 
572
                pending_inventory_records.append(((revision_id,), metadata, bytes))
499
573
            if repo_kind == 'revision':
500
574
                target_revision = revision_id
501
575
                self._install_revision(revision_id, metadata, bytes)
503
577
                self._install_signature(revision_id, metadata, bytes)
504
578
            if repo_kind == 'file':
505
579
                current_file = file_id
506
 
                if current_versionedfile is None:
507
 
                    current_versionedfile = \
508
 
                        self._repository.weave_store.get_weave_or_empty(
509
 
                        file_id, self._repository.get_transaction())
510
 
                    pending_file_records = []
511
 
                if revision_id in current_versionedfile:
512
 
                    continue
513
 
                pending_file_records.append((revision_id, metadata, bytes))
514
 
        self._install_mp_records(current_versionedfile, pending_file_records)
 
580
                pending_file_records.append(((file_id, revision_id), metadata, bytes))
 
581
        self._install_mp_records_keys(self._repository.texts, pending_file_records)
515
582
        return target_revision
516
583
 
517
584
    def _handle_info(self, info):
532
599
                      records if r not in versionedfile]
533
600
        versionedfile.add_mpdiffs(vf_records)
534
601
 
535
 
    def _install_inventory_records(self, vf, records):
536
 
        if self._info['serializer'] == self._repository._serializer.format_num:
537
 
            return self._install_mp_records(vf, records)
538
 
        for revision_id, metadata, bytes in records:
539
 
            parent_ids = metadata['parents']
540
 
            parents = [self._repository.get_inventory(p)
541
 
                       for p in parent_ids]
542
 
            p_texts = [self._source_serializer.write_inventory_to_string(p)
543
 
                       for p in parents]
544
 
            target_lines = multiparent.MultiParent.from_patch(bytes).to_lines(
545
 
                p_texts)
546
 
            sha1 = osutils.sha_strings(target_lines)
547
 
            if sha1 != metadata['sha1']:
548
 
                raise errors.BadBundle("Can't convert to target format")
549
 
            target_inv = self._source_serializer.read_inventory_from_string(
550
 
                ''.join(target_lines))
551
 
            self._handle_root(target_inv, parent_ids)
552
 
            try:
553
 
                self._repository.add_inventory(revision_id, target_inv,
554
 
                                               parent_ids)
555
 
            except errors.UnsupportedInventoryKind:
556
 
                raise errors.IncompatibleRevision(repr(self._repository))
 
602
    def _install_mp_records_keys(self, versionedfile, records):
 
603
        d_func = multiparent.MultiParent.from_patch
 
604
        vf_records = []
 
605
        for key, meta, text in records:
 
606
            # Adapt to tuple interface: A length two key is a file_id,
 
607
            # revision_id pair, a length 1 key is a
 
608
            # revision/signature/inventory. We need to do this because
 
609
            # the metadata extraction from the bundle has not yet been updated
 
610
            # to use the consistent tuple interface itself.
 
611
            if len(key) == 2:
 
612
                prefix = key[:1]
 
613
            else:
 
614
                prefix = ()
 
615
            parents = [prefix + (parent,) for parent in meta['parents']]
 
616
            vf_records.append((key, parents, meta['sha1'], d_func(text)))
 
617
        versionedfile.add_mpdiffs(vf_records)
 
618
 
 
619
    def _get_parent_inventory_texts(self, inventory_text_cache,
 
620
                                    inventory_cache, parent_ids):
 
621
        cached_parent_texts = {}
 
622
        remaining_parent_ids = []
 
623
        for parent_id in parent_ids:
 
624
            p_text = inventory_text_cache.get(parent_id, None)
 
625
            if p_text is None:
 
626
                remaining_parent_ids.append(parent_id)
 
627
            else:
 
628
                cached_parent_texts[parent_id] = p_text
 
629
        ghosts = ()
 
630
        # TODO: Use inventory_cache to grab inventories we already have in
 
631
        #       memory
 
632
        if remaining_parent_ids:
 
633
            # first determine what keys are actually present in the local
 
634
            # inventories object (don't use revisions as they haven't been
 
635
            # installed yet.)
 
636
            parent_keys = [(r,) for r in remaining_parent_ids]
 
637
            present_parent_map = self._repository.inventories.get_parent_map(
 
638
                                        parent_keys)
 
639
            present_parent_ids = []
 
640
            ghosts = set()
 
641
            for p_id in remaining_parent_ids:
 
642
                if (p_id,) in present_parent_map:
 
643
                    present_parent_ids.append(p_id)
 
644
                else:
 
645
                    ghosts.add(p_id)
 
646
            to_string = self._source_serializer.write_inventory_to_string
 
647
            for parent_inv in self._repository.iter_inventories(
 
648
                                    present_parent_ids):
 
649
                p_text = to_string(parent_inv)
 
650
                inventory_cache[parent_inv.revision_id] = parent_inv
 
651
                cached_parent_texts[parent_inv.revision_id] = p_text
 
652
                inventory_text_cache[parent_inv.revision_id] = p_text
 
653
 
 
654
        parent_texts = [cached_parent_texts[parent_id]
 
655
                        for parent_id in parent_ids
 
656
                         if parent_id not in ghosts]
 
657
        return parent_texts
 
658
 
 
659
    def _install_inventory_records(self, records):
 
660
        if (self._info['serializer'] == self._repository._serializer.format_num
 
661
            and self._repository._serializer.support_altered_by_hack):
 
662
            return self._install_mp_records_keys(self._repository.inventories,
 
663
                records)
 
664
        # Use a 10MB text cache, since these are string xml inventories. Note
 
665
        # that 10MB is fairly small for large projects (a single inventory can
 
666
        # be >5MB). Another possibility is to cache 10-20 inventory texts
 
667
        # instead
 
668
        inventory_text_cache = lru_cache.LRUSizeCache(10*1024*1024)
 
669
        # Also cache the in-memory representation. This allows us to create
 
670
        # inventory deltas to apply rather than calling add_inventory from
 
671
        # scratch each time.
 
672
        inventory_cache = lru_cache.LRUCache(10)
 
673
        pb = ui.ui_factory.nested_progress_bar()
 
674
        try:
 
675
            num_records = len(records)
 
676
            for idx, (key, metadata, bytes) in enumerate(records):
 
677
                pb.update('installing inventory', idx, num_records)
 
678
                revision_id = key[-1]
 
679
                parent_ids = metadata['parents']
 
680
                # Note: This assumes the local ghosts are identical to the
 
681
                #       ghosts in the source, as the Bundle serialization
 
682
                #       format doesn't record ghosts.
 
683
                p_texts = self._get_parent_inventory_texts(inventory_text_cache,
 
684
                                                           inventory_cache,
 
685
                                                           parent_ids)
 
686
                # Why does to_lines() take strings as the source, it seems that
 
687
                # it would have to cast to a list of lines, which we get back
 
688
                # as lines and then cast back to a string.
 
689
                target_lines = multiparent.MultiParent.from_patch(bytes
 
690
                            ).to_lines(p_texts)
 
691
                inv_text = ''.join(target_lines)
 
692
                del target_lines
 
693
                sha1 = osutils.sha_string(inv_text)
 
694
                if sha1 != metadata['sha1']:
 
695
                    raise errors.BadBundle("Can't convert to target format")
 
696
                # Add this to the cache so we don't have to extract it again.
 
697
                inventory_text_cache[revision_id] = inv_text
 
698
                target_inv = self._source_serializer.read_inventory_from_string(
 
699
                    inv_text)
 
700
                self._handle_root(target_inv, parent_ids)
 
701
                parent_inv = None
 
702
                if parent_ids:
 
703
                    parent_inv = inventory_cache.get(parent_ids[0], None)
 
704
                try:
 
705
                    if parent_inv is None:
 
706
                        self._repository.add_inventory(revision_id, target_inv,
 
707
                                                       parent_ids)
 
708
                    else:
 
709
                        delta = target_inv._make_delta(parent_inv)
 
710
                        self._repository.add_inventory_by_delta(parent_ids[0],
 
711
                            delta, revision_id, parent_ids)
 
712
                except errors.UnsupportedInventoryKind:
 
713
                    raise errors.IncompatibleRevision(repr(self._repository))
 
714
                inventory_cache[revision_id] = target_inv
 
715
        finally:
 
716
            pb.finished()
557
717
 
558
718
    def _handle_root(self, target_inv, parent_ids):
559
719
        revision_id = target_inv.revision_id
560
720
        if self.update_root:
561
 
            target_inv.root.revision = revision_id
562
 
            store = self._repository.weave_store
563
 
            transaction = self._repository.get_transaction()
564
 
            vf = store.get_weave_or_empty(target_inv.root.file_id, transaction)
565
 
            vf.add_lines(revision_id, parent_ids, [])
 
721
            text_key = (target_inv.root.file_id, revision_id)
 
722
            parent_keys = [(target_inv.root.file_id, parent) for
 
723
                parent in parent_ids]
 
724
            self._repository.texts.add_lines(text_key, parent_keys, [])
566
725
        elif not self._repository.supports_rich_root():
567
726
            if target_inv.root.revision != revision_id:
568
727
                raise errors.IncompatibleRevision(repr(self._repository))
569
728
 
570
 
 
571
729
    def _install_revision(self, revision_id, metadata, text):
572
730
        if self._repository.has_revision(revision_id):
573
731
            return
574
 
        self._repository._add_revision_text(revision_id, text)
 
732
        revision = self._source_serializer.read_revision_from_string(text)
 
733
        self._repository.add_revision(revision.revision_id, revision)
575
734
 
576
735
    def _install_signature(self, revision_id, metadata, text):
577
736
        transaction = self._repository.get_transaction()
578
 
        if self._repository._revision_store.has_signature(revision_id,
579
 
                                                          transaction):
 
737
        if self._repository.has_signature_for_revision_id(revision_id):
580
738
            return
581
 
        self._repository._revision_store.add_revision_signature_text(
582
 
            revision_id, text, transaction)
 
739
        self._repository.add_signature_text(revision_id, text)