/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

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

  • Committer: Martin Pool
  • Date: 2009-07-27 06:28:35 UTC
  • mto: This revision was merged to the branch mainline in revision 4587.
  • Revision ID: mbp@sourcefrog.net-20090727062835-o66p8it658tq1sma
Add CountedLock.get_physical_lock_status

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
 
1
# Copyright (C) 2007 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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
 
17
from cStringIO import StringIO
19
18
import bz2
20
19
import re
21
20
 
22
 
from .... import (
23
 
    bencode,
 
21
from bzrlib import (
 
22
    diff,
24
23
    errors,
25
24
    iterablefile,
26
 
    lru_cache,
27
25
    multiparent,
28
26
    osutils,
29
 
    repository as _mod_repository,
 
27
    pack,
30
28
    revision as _mod_revision,
31
29
    trace,
32
 
    ui,
33
 
    )
34
 
from ... import (
35
 
    pack,
36
30
    serializer,
37
 
    versionedfile as _mod_versionedfile,
38
 
    )
39
 
from .. import bundle_data, serializer as bundle_serializer
40
 
from ....i18n import ngettext
41
 
from ....sixish import (
42
 
    BytesIO,
43
 
    viewitems,
44
 
    )
45
 
 
46
 
 
47
 
class _MPDiffInventoryGenerator(_mod_versionedfile._MPDiffGenerator):
48
 
    """Generate Inventory diffs serialized inventories."""
49
 
 
50
 
    def __init__(self, repo, inventory_keys):
51
 
        super(_MPDiffInventoryGenerator, self).__init__(repo.inventories,
52
 
                                                        inventory_keys)
53
 
        self.repo = repo
54
 
        self.sha1s = {}
55
 
 
56
 
    def iter_diffs(self):
57
 
        """Compute the diffs one at a time."""
58
 
        # This is instead of compute_diffs() since we guarantee our ordering of
59
 
        # inventories, we don't have to do any buffering
60
 
        self._find_needed_keys()
61
 
        # We actually use a slightly different ordering. We grab all of the
62
 
        # parents first, and then grab the ordered requests.
63
 
        needed_ids = [k[-1] for k in self.present_parents]
64
 
        needed_ids.extend([k[-1] for k in self.ordered_keys])
65
 
        inv_to_lines = self.repo._serializer.write_inventory_to_chunks
66
 
        for inv in self.repo.iter_inventories(needed_ids):
67
 
            revision_id = inv.revision_id
68
 
            key = (revision_id,)
69
 
            if key in self.present_parents:
70
 
                # Not a key we will transmit, which is a shame, since because
71
 
                # of that bundles don't work with stacked branches
72
 
                parent_ids = None
73
 
            else:
74
 
                parent_ids = [k[-1] for k in self.parent_map[key]]
75
 
            as_chunks = inv_to_lines(inv)
76
 
            self._process_one_record(key, as_chunks)
77
 
            if parent_ids is None:
78
 
                continue
79
 
            diff = self.diffs.pop(key)
80
 
            sha1 = osutils.sha_strings(as_chunks)
81
 
            yield revision_id, parent_ids, sha1, diff
 
31
    )
 
32
from bzrlib.bundle import bundle_data, serializer as bundle_serializer
 
33
from bzrlib import bencode
82
34
 
83
35
 
84
36
class BundleWriter(object):
102
54
 
103
55
    def begin(self):
104
56
        """Start writing the bundle"""
105
 
        self._fileobj.write(bundle_serializer._get_bundle_header('4'))
106
 
        self._fileobj.write(b'#\n')
 
57
        self._fileobj.write(bundle_serializer._get_bundle_header(
 
58
            bundle_serializer.v4_string))
 
59
        self._fileobj.write('#\n')
107
60
        self._container.begin()
108
61
 
109
62
    def end(self):
123
76
        :revision_id: The revision id of the mpdiff being added.
124
77
        :file_id: The file-id of the file, or None for inventories.
125
78
        """
126
 
        metadata = {b'parents': parents,
127
 
                    b'storage_kind': b'mpdiff',
128
 
                    b'sha1': sha1}
 
79
        metadata = {'parents': parents,
 
80
                    'storage_kind': 'mpdiff',
 
81
                    'sha1': sha1}
129
82
        self._add_record(mp_bytes, metadata, repo_kind, revision_id, file_id)
130
83
 
131
84
    def add_fulltext_record(self, bytes, parents, repo_kind, revision_id):
137
90
            'signature'
138
91
        :revision_id: The revision id of the fulltext being added.
139
92
        """
140
 
        metadata = {b'parents': parents,
141
 
                    b'storage_kind': b'mpdiff'}
142
 
        self._add_record(bytes, {b'parents': parents,
143
 
                                 b'storage_kind': b'fulltext'}, repo_kind, revision_id, None)
 
93
        metadata = {'parents': parents,
 
94
                    'storage_kind': 'mpdiff'}
 
95
        self._add_record(bytes, {'parents': parents,
 
96
            'storage_kind': 'fulltext'}, repo_kind, revision_id, None)
144
97
 
145
 
    def add_info_record(self, kwargs):
 
98
    def add_info_record(self, **kwargs):
146
99
        """Add an info record to the bundle
147
100
 
148
101
        Any parameters may be supplied, except 'self' and 'storage_kind'.
149
102
        Values must be lists, strings, integers, dicts, or a combination.
150
103
        """
151
 
        kwargs[b'storage_kind'] = b'header'
 
104
        kwargs['storage_kind'] = 'header'
152
105
        self._add_record(None, kwargs, 'info', None, None)
153
106
 
154
107
    @staticmethod
155
108
    def encode_name(content_kind, revision_id, file_id=None):
156
109
        """Encode semantic ids as a container name"""
157
110
        if content_kind not in ('revision', 'file', 'inventory', 'signature',
158
 
                                'info'):
 
111
                'info'):
159
112
            raise ValueError(content_kind)
160
113
        if content_kind == 'file':
161
114
            if file_id is None:
168
121
                raise AssertionError()
169
122
        elif revision_id is None:
170
123
            raise AssertionError()
171
 
        names = [n.replace(b'/', b'//') for n in
172
 
                 (content_kind.encode('ascii'), revision_id, file_id) if n is not None]
173
 
        return b'/'.join(names)
 
124
        names = [n.replace('/', '//') for n in
 
125
                 (content_kind, revision_id, file_id) if n is not None]
 
126
        return '/'.join(names)
174
127
 
175
128
    def _add_record(self, bytes, metadata, repo_kind, revision_id, file_id):
176
129
        """Add a bundle record to the container.
181
134
        """
182
135
        name = self.encode_name(repo_kind, revision_id, file_id)
183
136
        encoded_metadata = bencode.bencode(metadata)
184
 
        self._container.add_bytes_record([encoded_metadata], len(encoded_metadata), [(name, )])
185
 
        if metadata[b'storage_kind'] != b'header':
186
 
            self._container.add_bytes_record([bytes], len(bytes), [])
 
137
        self._container.add_bytes_record(encoded_metadata, [(name, )])
 
138
        if metadata['storage_kind'] != 'header':
 
139
            self._container.add_bytes_record(bytes, [])
187
140
 
188
141
 
189
142
class BundleReader(object):
209
162
        if stream_input:
210
163
            source_file = iterablefile.IterableFile(self.iter_decode(fileobj))
211
164
        else:
212
 
            source_file = BytesIO(bz2.decompress(fileobj.read()))
 
165
            source_file = StringIO(bz2.decompress(fileobj.read()))
213
166
        self._container_file = source_file
214
167
 
215
168
    @staticmethod
228
181
 
229
182
        :retval: content_kind, revision_id, file_id
230
183
        """
231
 
        segments = re.split(b'(//?)', name)
232
 
        names = [b'']
 
184
        segments = re.split('(//?)', name)
 
185
        names = ['']
233
186
        for segment in segments:
234
 
            if segment == b'//':
235
 
                names[-1] += b'/'
236
 
            elif segment == b'/':
237
 
                names.append(b'')
 
187
            if segment == '//':
 
188
                names[-1] += '/'
 
189
            elif segment == '/':
 
190
                names.append('')
238
191
            else:
239
192
                names[-1] += segment
240
193
        content_kind = names[0]
244
197
            revision_id = names[1]
245
198
        if len(names) > 2:
246
199
            file_id = names[2]
247
 
        return content_kind.decode('ascii'), revision_id, file_id
 
200
        return content_kind, revision_id, file_id
248
201
 
249
202
    def iter_records(self):
250
203
        """Iterate through bundle records
258
211
                raise errors.BadBundle('Record has %d names instead of 1'
259
212
                                       % len(names))
260
213
            metadata = bencode.bdecode(bytes)
261
 
            if metadata[b'storage_kind'] == b'header':
 
214
            if metadata['storage_kind'] == 'header':
262
215
                bytes = None
263
216
            else:
264
 
                _unused, bytes = next(iterator)
 
217
                _unused, bytes = iterator.next()
265
218
            yield (bytes, metadata) + self.decode_name(names[0][0])
266
219
 
267
220
 
268
221
class BundleSerializerV4(bundle_serializer.BundleSerializer):
269
222
    """Implement the high-level bundle interface"""
270
223
 
 
224
    def write(self, repository, revision_ids, forced_bases, fileobj):
 
225
        """Write a bundle to a file-like object
 
226
 
 
227
        For backwards-compatibility only
 
228
        """
 
229
        write_op = BundleWriteOperation.from_old_args(repository, revision_ids,
 
230
                                                      forced_bases, fileobj)
 
231
        return write_op.do_write()
 
232
 
271
233
    def write_bundle(self, repository, target, base, fileobj):
272
234
        """Write a bundle to a file object
273
235
 
277
239
            at.
278
240
        :param fileobj: The file-like object to write to
279
241
        """
280
 
        write_op = BundleWriteOperation(base, target, repository, fileobj)
 
242
        write_op =  BundleWriteOperation(base, target, repository, fileobj)
281
243
        return write_op.do_write()
282
244
 
283
245
    def read(self, file):
288
250
    @staticmethod
289
251
    def get_source_serializer(info):
290
252
        """Retrieve the serializer for a given info object"""
291
 
        return serializer.format_registry.get(info[b'serializer'].decode('ascii'))
 
253
        return serializer.format_registry.get(info['serializer'])
292
254
 
293
255
 
294
256
class BundleWriteOperation(object):
295
257
    """Perform the operation of writing revisions to a bundle"""
296
258
 
 
259
    @classmethod
 
260
    def from_old_args(cls, repository, revision_ids, forced_bases, fileobj):
 
261
        """Create a BundleWriteOperation from old-style arguments"""
 
262
        base, target = cls.get_base_target(revision_ids, forced_bases,
 
263
                                           repository)
 
264
        return BundleWriteOperation(base, target, repository, fileobj,
 
265
                                    revision_ids)
 
266
 
297
267
    def __init__(self, base, target, repository, fileobj, revision_ids=None):
298
268
        self.base = base
299
269
        self.target = target
308
278
            # Strip ghosts
309
279
            parents = graph.get_parent_map(revision_ids)
310
280
            self.revision_ids = [r for r in revision_ids if r in parents]
311
 
        self.revision_keys = {(revid,) for revid in self.revision_ids}
 
281
        self.revision_keys = set([(revid,) for revid in self.revision_ids])
312
282
 
313
283
    def do_write(self):
314
284
        """Write all data to the bundle"""
315
 
        trace.note(ngettext('Bundling %d revision.', 'Bundling %d revisions.',
316
 
                            len(self.revision_ids)), len(self.revision_ids))
317
 
        with self.repository.lock_read():
 
285
        trace.note('Bundling %d revision(s).', len(self.revision_ids))
 
286
        self.repository.lock_read()
 
287
        try:
318
288
            self.bundle.begin()
319
289
            self.write_info()
320
290
            self.write_files()
321
291
            self.write_revisions()
322
292
            self.bundle.end()
 
293
        finally:
 
294
            self.repository.unlock()
323
295
        return self.revision_ids
324
296
 
325
297
    def write_info(self):
327
299
        serializer_format = self.repository.get_serializer_format()
328
300
        supports_rich_root = {True: 1, False: 0}[
329
301
            self.repository.supports_rich_root()]
330
 
        self.bundle.add_info_record({b'serializer': serializer_format,
331
 
                                     b'supports_rich_root': supports_rich_root})
 
302
        self.bundle.add_info_record(serializer=serializer_format,
 
303
                                    supports_rich_root=supports_rich_root)
332
304
 
333
305
    def write_files(self):
334
306
        """Write bundle records for all revisions of all files"""
335
307
        text_keys = []
336
308
        altered_fileids = self.repository.fileids_altered_by_revision_ids(
337
 
            self.revision_ids)
338
 
        for file_id, revision_ids in viewitems(altered_fileids):
 
309
                self.revision_ids)
 
310
        for file_id, revision_ids in altered_fileids.iteritems():
339
311
            for revision_id in revision_ids:
340
312
                text_keys.append((file_id, revision_id))
341
313
        self._add_mp_records_keys('file', self.repository.texts, text_keys)
343
315
    def write_revisions(self):
344
316
        """Write bundle records for all revisions and signatures"""
345
317
        inv_vf = self.repository.inventories
346
 
        topological_order = [key[-1] for key in multiparent.topo_iter_keys(
347
 
            inv_vf, self.revision_keys)]
348
 
        revision_order = topological_order
 
318
        revision_order = [key[-1] for key in multiparent.topo_iter_keys(inv_vf,
 
319
            self.revision_keys)]
349
320
        if self.target is not None and self.target in self.revision_ids:
350
 
            # Make sure the target revision is always the last entry
351
 
            revision_order = list(topological_order)
352
321
            revision_order.remove(self.target)
353
322
            revision_order.append(self.target)
354
 
        if self.repository._serializer.support_altered_by_hack:
355
 
            # Repositories that support_altered_by_hack means that
356
 
            # inventories.make_mpdiffs() contains all the data about the tree
357
 
            # shape. Formats without support_altered_by_hack require
358
 
            # chk_bytes/etc, so we use a different code path.
359
 
            self._add_mp_records_keys('inventory', inv_vf,
360
 
                                      [(revid,) for revid in topological_order])
361
 
        else:
362
 
            # Inventories should always be added in pure-topological order, so
363
 
            # that we can apply the mpdiff for the child to the parent texts.
364
 
            self._add_inventory_mpdiffs_from_serializer(topological_order)
365
 
        self._add_revision_texts(revision_order)
366
 
 
367
 
    def _add_inventory_mpdiffs_from_serializer(self, revision_order):
368
 
        """Generate mpdiffs by serializing inventories.
369
 
 
370
 
        The current repository only has part of the tree shape information in
371
 
        the 'inventories' vf. So we use serializer.write_inventory_to_lines to
372
 
        get a 'full' representation of the tree shape, and then generate
373
 
        mpdiffs on that data stream. This stream can then be reconstructed on
374
 
        the other side.
375
 
        """
376
 
        inventory_key_order = [(r,) for r in revision_order]
377
 
        generator = _MPDiffInventoryGenerator(self.repository,
378
 
                                              inventory_key_order)
379
 
        for revision_id, parent_ids, sha1, diff in generator.iter_diffs():
380
 
            text = b''.join(diff.to_patch())
381
 
            self.bundle.add_multiparent_record(text, sha1, parent_ids,
382
 
                                               'inventory', revision_id, None)
383
 
 
384
 
    def _add_revision_texts(self, revision_order):
 
323
        self._add_mp_records_keys('inventory', inv_vf, [(revid,) for revid in revision_order])
385
324
        parent_map = self.repository.get_parent_map(revision_order)
386
 
        revision_to_bytes = self.repository._serializer.write_revision_to_string
 
325
        revision_to_str = self.repository._serializer.write_revision_to_string
387
326
        revisions = self.repository.get_revisions(revision_order)
388
327
        for revision in revisions:
389
328
            revision_id = revision.revision_id
390
329
            parents = parent_map.get(revision_id, None)
391
 
            revision_text = revision_to_bytes(revision)
 
330
            revision_text = revision_to_str(revision)
392
331
            self.bundle.add_fulltext_record(revision_text, parents,
393
 
                                            'revision', revision_id)
 
332
                                       'revision', revision_id)
394
333
            try:
395
334
                self.bundle.add_fulltext_record(
396
335
                    self.repository.get_signature_text(
397
 
                        revision_id), parents, 'signature', revision_id)
 
336
                    revision_id), parents, 'signature', revision_id)
398
337
            except errors.NoSuchRevision:
399
338
                pass
400
339
 
422
361
        for mpdiff, item_key, in zip(mpdiffs, ordered_keys):
423
362
            sha1 = sha1s[item_key]
424
363
            parents = [key[-1] for key in parent_map[item_key]]
425
 
            text = b''.join(mpdiff.to_patch())
 
364
            text = ''.join(mpdiff.to_patch())
426
365
            # Infer file id records as appropriate.
427
366
            if len(item_key) == 2:
428
367
                file_id = item_key[0]
435
374
class BundleInfoV4(object):
436
375
 
437
376
    """Provide (most of) the BundleInfo interface"""
438
 
 
439
377
    def __init__(self, fileobj, serializer):
440
378
        self._fileobj = fileobj
441
379
        self._serializer = serializer
453
391
            all into memory at once.  Reading it into memory all at once is
454
392
            (currently) faster.
455
393
        """
456
 
        with repository.lock_write():
 
394
        repository.lock_write()
 
395
        try:
457
396
            ri = RevisionInstaller(self.get_bundle_reader(stream_input),
458
397
                                   self._serializer, repository)
459
398
            return ri.install()
 
399
        finally:
 
400
            repository.unlock()
460
401
 
461
402
    def get_merge_request(self, target_repo):
462
403
        """Provide data for performing a merge
480
421
            self.__real_revisions = []
481
422
            bundle_reader = self.get_bundle_reader()
482
423
            for bytes, metadata, repo_kind, revision_id, file_id in \
483
 
                    bundle_reader.iter_records():
 
424
                bundle_reader.iter_records():
484
425
                if repo_kind == 'info':
485
426
                    serializer =\
486
427
                        self._serializer.get_source_serializer(metadata)
520
461
 
521
462
        Must be called with the Repository locked.
522
463
        """
523
 
        with _mod_repository.WriteGroup(self._repository):
524
 
            return self._install_in_write_group()
 
464
        self._repository.start_write_group()
 
465
        try:
 
466
            result = self._install_in_write_group()
 
467
        except:
 
468
            self._repository.abort_write_group()
 
469
            raise
 
470
        self._repository.commit_write_group()
 
471
        return result
525
472
 
526
473
    def _install_in_write_group(self):
527
474
        current_file = None
532
479
        added_inv = set()
533
480
        target_revision = None
534
481
        for bytes, metadata, repo_kind, revision_id, file_id in\
535
 
                self._container.iter_records():
 
482
            self._container.iter_records():
536
483
            if repo_kind == 'info':
537
484
                if self._info is not None:
538
485
                    raise AssertionError()
539
486
                self._handle_info(metadata)
540
487
            if (pending_file_records and
541
 
                    (repo_kind, file_id) != ('file', current_file)):
 
488
                (repo_kind, file_id) != ('file', current_file)):
542
489
                # Flush the data for a single file - prevents memory
543
490
                # spiking due to buffering all files in memory.
544
491
                self._install_mp_records_keys(self._repository.texts,
545
 
                                              pending_file_records)
 
492
                    pending_file_records)
546
493
                current_file = None
547
494
                del pending_file_records[:]
548
495
            if len(pending_inventory_records) > 0 and repo_kind != 'inventory':
549
496
                self._install_inventory_records(pending_inventory_records)
550
497
                pending_inventory_records = []
551
498
            if repo_kind == 'inventory':
552
 
                pending_inventory_records.append(
553
 
                    ((revision_id,), metadata, bytes))
 
499
                pending_inventory_records.append(((revision_id,), metadata, bytes))
554
500
            if repo_kind == 'revision':
555
501
                target_revision = revision_id
556
502
                self._install_revision(revision_id, metadata, bytes)
558
504
                self._install_signature(revision_id, metadata, bytes)
559
505
            if repo_kind == 'file':
560
506
                current_file = file_id
561
 
                pending_file_records.append(
562
 
                    ((file_id, revision_id), metadata, bytes))
563
 
        self._install_mp_records_keys(
564
 
            self._repository.texts, pending_file_records)
 
507
                pending_file_records.append(((file_id, revision_id), metadata, bytes))
 
508
        self._install_mp_records_keys(self._repository.texts, pending_file_records)
565
509
        return target_revision
566
510
 
567
511
    def _handle_info(self, info):
568
512
        """Extract data from an info record"""
569
513
        self._info = info
570
514
        self._source_serializer = self._serializer.get_source_serializer(info)
571
 
        if (info[b'supports_rich_root'] == 0 and
572
 
                self._repository.supports_rich_root()):
 
515
        if (info['supports_rich_root'] == 0 and
 
516
            self._repository.supports_rich_root()):
573
517
            self.update_root = True
574
518
        else:
575
519
            self.update_root = False
595
539
                prefix = key[:1]
596
540
            else:
597
541
                prefix = ()
598
 
            parents = [prefix + (parent,) for parent in meta[b'parents']]
599
 
            vf_records.append((key, parents, meta[b'sha1'], d_func(text)))
 
542
            parents = [prefix + (parent,) for parent in meta['parents']]
 
543
            vf_records.append((key, parents, meta['sha1'], d_func(text)))
600
544
        versionedfile.add_mpdiffs(vf_records)
601
545
 
602
 
    def _get_parent_inventory_texts(self, inventory_text_cache,
603
 
                                    inventory_cache, parent_ids):
604
 
        cached_parent_texts = {}
605
 
        remaining_parent_ids = []
606
 
        for parent_id in parent_ids:
607
 
            p_text = inventory_text_cache.get(parent_id, None)
608
 
            if p_text is None:
609
 
                remaining_parent_ids.append(parent_id)
610
 
            else:
611
 
                cached_parent_texts[parent_id] = p_text
612
 
        ghosts = ()
613
 
        # TODO: Use inventory_cache to grab inventories we already have in
614
 
        #       memory
615
 
        if remaining_parent_ids:
616
 
            # first determine what keys are actually present in the local
617
 
            # inventories object (don't use revisions as they haven't been
618
 
            # installed yet.)
619
 
            parent_keys = [(r,) for r in remaining_parent_ids]
620
 
            present_parent_map = self._repository.inventories.get_parent_map(
621
 
                parent_keys)
622
 
            present_parent_ids = []
623
 
            ghosts = set()
624
 
            for p_id in remaining_parent_ids:
625
 
                if (p_id,) in present_parent_map:
626
 
                    present_parent_ids.append(p_id)
627
 
                else:
628
 
                    ghosts.add(p_id)
629
 
            to_lines = self._source_serializer.write_inventory_to_chunks
630
 
            for parent_inv in self._repository.iter_inventories(
631
 
                    present_parent_ids):
632
 
                p_text = b''.join(to_lines(parent_inv))
633
 
                inventory_cache[parent_inv.revision_id] = parent_inv
634
 
                cached_parent_texts[parent_inv.revision_id] = p_text
635
 
                inventory_text_cache[parent_inv.revision_id] = p_text
636
 
 
637
 
        parent_texts = [cached_parent_texts[parent_id]
638
 
                        for parent_id in parent_ids
639
 
                        if parent_id not in ghosts]
640
 
        return parent_texts
641
 
 
642
546
    def _install_inventory_records(self, records):
643
 
        if (self._info[b'serializer'] == self._repository._serializer.format_num
644
 
                and self._repository._serializer.support_altered_by_hack):
 
547
        if self._info['serializer'] == self._repository._serializer.format_num:
645
548
            return self._install_mp_records_keys(self._repository.inventories,
646
 
                                                 records)
647
 
        # Use a 10MB text cache, since these are string xml inventories. Note
648
 
        # that 10MB is fairly small for large projects (a single inventory can
649
 
        # be >5MB). Another possibility is to cache 10-20 inventory texts
650
 
        # instead
651
 
        inventory_text_cache = lru_cache.LRUSizeCache(10 * 1024 * 1024)
652
 
        # Also cache the in-memory representation. This allows us to create
653
 
        # inventory deltas to apply rather than calling add_inventory from
654
 
        # scratch each time.
655
 
        inventory_cache = lru_cache.LRUCache(10)
656
 
        with ui.ui_factory.nested_progress_bar() as pb:
657
 
            num_records = len(records)
658
 
            for idx, (key, metadata, bytes) in enumerate(records):
659
 
                pb.update('installing inventory', idx, num_records)
660
 
                revision_id = key[-1]
661
 
                parent_ids = metadata[b'parents']
662
 
                # Note: This assumes the local ghosts are identical to the
663
 
                #       ghosts in the source, as the Bundle serialization
664
 
                #       format doesn't record ghosts.
665
 
                p_texts = self._get_parent_inventory_texts(inventory_text_cache,
666
 
                                                           inventory_cache,
667
 
                                                           parent_ids)
668
 
                # Why does to_lines() take strings as the source, it seems that
669
 
                # it would have to cast to a list of lines, which we get back
670
 
                # as lines and then cast back to a string.
671
 
                target_lines = multiparent.MultiParent.from_patch(bytes
672
 
                                                                  ).to_lines(p_texts)
673
 
                sha1 = osutils.sha_strings(target_lines)
674
 
                if sha1 != metadata[b'sha1']:
675
 
                    raise errors.BadBundle("Can't convert to target format")
676
 
                # Add this to the cache so we don't have to extract it again.
677
 
                inventory_text_cache[revision_id] = b''.join(target_lines)
678
 
                target_inv = self._source_serializer.read_inventory_from_lines(
679
 
                    target_lines)
680
 
                del target_lines
681
 
                self._handle_root(target_inv, parent_ids)
682
 
                parent_inv = None
683
 
                if parent_ids:
684
 
                    parent_inv = inventory_cache.get(parent_ids[0], None)
685
 
                try:
686
 
                    if parent_inv is None:
687
 
                        self._repository.add_inventory(revision_id, target_inv,
688
 
                                                       parent_ids)
689
 
                    else:
690
 
                        delta = target_inv._make_delta(parent_inv)
691
 
                        self._repository.add_inventory_by_delta(parent_ids[0],
692
 
                                                                delta, revision_id, parent_ids)
693
 
                except errors.UnsupportedInventoryKind:
694
 
                    raise errors.IncompatibleRevision(repr(self._repository))
695
 
                inventory_cache[revision_id] = target_inv
 
549
                records)
 
550
        for key, metadata, bytes in records:
 
551
            revision_id = key[-1]
 
552
            parent_ids = metadata['parents']
 
553
            parents = [self._repository.get_inventory(p)
 
554
                       for p in parent_ids]
 
555
            p_texts = [self._source_serializer.write_inventory_to_string(p)
 
556
                       for p in parents]
 
557
            target_lines = multiparent.MultiParent.from_patch(bytes).to_lines(
 
558
                p_texts)
 
559
            sha1 = osutils.sha_strings(target_lines)
 
560
            if sha1 != metadata['sha1']:
 
561
                raise errors.BadBundle("Can't convert to target format")
 
562
            target_inv = self._source_serializer.read_inventory_from_string(
 
563
                ''.join(target_lines))
 
564
            self._handle_root(target_inv, parent_ids)
 
565
            try:
 
566
                self._repository.add_inventory(revision_id, target_inv,
 
567
                                               parent_ids)
 
568
            except errors.UnsupportedInventoryKind:
 
569
                raise errors.IncompatibleRevision(repr(self._repository))
696
570
 
697
571
    def _handle_root(self, target_inv, parent_ids):
698
572
        revision_id = target_inv.revision_id
699
573
        if self.update_root:
700
574
            text_key = (target_inv.root.file_id, revision_id)
701
575
            parent_keys = [(target_inv.root.file_id, parent) for
702
 
                           parent in parent_ids]
 
576
                parent in parent_ids]
703
577
            self._repository.texts.add_lines(text_key, parent_keys, [])
704
578
        elif not self._repository.supports_rich_root():
705
579
            if target_inv.root.revision != revision_id: