/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: Robert Collins
  • Date: 2010-05-05 00:05:29 UTC
  • mto: This revision was merged to the branch mainline in revision 5206.
  • Revision ID: robertc@robertcollins.net-20100505000529-ltmllyms5watqj5u
Make 'pydoc bzrlib.tests.build_tree_shape' useful.

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
25
    lru_cache,
27
26
    multiparent,
28
27
    osutils,
29
 
    repository as _mod_repository,
 
28
    pack,
30
29
    revision as _mod_revision,
 
30
    serializer,
31
31
    trace,
32
32
    ui,
33
33
    )
34
 
from ... import (
35
 
    pack,
36
 
    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
 
34
from bzrlib.bundle import bundle_data, serializer as bundle_serializer
 
35
from bzrlib import bencode
82
36
 
83
37
 
84
38
class BundleWriter(object):
102
56
 
103
57
    def begin(self):
104
58
        """Start writing the bundle"""
105
 
        self._fileobj.write(bundle_serializer._get_bundle_header('4'))
106
 
        self._fileobj.write(b'#\n')
 
59
        self._fileobj.write(bundle_serializer._get_bundle_header(
 
60
            bundle_serializer.v4_string))
 
61
        self._fileobj.write('#\n')
107
62
        self._container.begin()
108
63
 
109
64
    def end(self):
123
78
        :revision_id: The revision id of the mpdiff being added.
124
79
        :file_id: The file-id of the file, or None for inventories.
125
80
        """
126
 
        metadata = {b'parents': parents,
127
 
                    b'storage_kind': b'mpdiff',
128
 
                    b'sha1': sha1}
 
81
        metadata = {'parents': parents,
 
82
                    'storage_kind': 'mpdiff',
 
83
                    'sha1': sha1}
129
84
        self._add_record(mp_bytes, metadata, repo_kind, revision_id, file_id)
130
85
 
131
86
    def add_fulltext_record(self, bytes, parents, repo_kind, revision_id):
137
92
            'signature'
138
93
        :revision_id: The revision id of the fulltext being added.
139
94
        """
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)
 
95
        metadata = {'parents': parents,
 
96
                    'storage_kind': 'mpdiff'}
 
97
        self._add_record(bytes, {'parents': parents,
 
98
            'storage_kind': 'fulltext'}, repo_kind, revision_id, None)
144
99
 
145
 
    def add_info_record(self, kwargs):
 
100
    def add_info_record(self, **kwargs):
146
101
        """Add an info record to the bundle
147
102
 
148
103
        Any parameters may be supplied, except 'self' and 'storage_kind'.
149
104
        Values must be lists, strings, integers, dicts, or a combination.
150
105
        """
151
 
        kwargs[b'storage_kind'] = b'header'
 
106
        kwargs['storage_kind'] = 'header'
152
107
        self._add_record(None, kwargs, 'info', None, None)
153
108
 
154
109
    @staticmethod
155
110
    def encode_name(content_kind, revision_id, file_id=None):
156
111
        """Encode semantic ids as a container name"""
157
112
        if content_kind not in ('revision', 'file', 'inventory', 'signature',
158
 
                                'info'):
 
113
                'info'):
159
114
            raise ValueError(content_kind)
160
115
        if content_kind == 'file':
161
116
            if file_id is None:
168
123
                raise AssertionError()
169
124
        elif revision_id is None:
170
125
            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)
 
126
        names = [n.replace('/', '//') for n in
 
127
                 (content_kind, revision_id, file_id) if n is not None]
 
128
        return '/'.join(names)
174
129
 
175
130
    def _add_record(self, bytes, metadata, repo_kind, revision_id, file_id):
176
131
        """Add a bundle record to the container.
181
136
        """
182
137
        name = self.encode_name(repo_kind, revision_id, file_id)
183
138
        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), [])
 
139
        self._container.add_bytes_record(encoded_metadata, [(name, )])
 
140
        if metadata['storage_kind'] != 'header':
 
141
            self._container.add_bytes_record(bytes, [])
187
142
 
188
143
 
189
144
class BundleReader(object):
209
164
        if stream_input:
210
165
            source_file = iterablefile.IterableFile(self.iter_decode(fileobj))
211
166
        else:
212
 
            source_file = BytesIO(bz2.decompress(fileobj.read()))
 
167
            source_file = StringIO(bz2.decompress(fileobj.read()))
213
168
        self._container_file = source_file
214
169
 
215
170
    @staticmethod
228
183
 
229
184
        :retval: content_kind, revision_id, file_id
230
185
        """
231
 
        segments = re.split(b'(//?)', name)
232
 
        names = [b'']
 
186
        segments = re.split('(//?)', name)
 
187
        names = ['']
233
188
        for segment in segments:
234
 
            if segment == b'//':
235
 
                names[-1] += b'/'
236
 
            elif segment == b'/':
237
 
                names.append(b'')
 
189
            if segment == '//':
 
190
                names[-1] += '/'
 
191
            elif segment == '/':
 
192
                names.append('')
238
193
            else:
239
194
                names[-1] += segment
240
195
        content_kind = names[0]
244
199
            revision_id = names[1]
245
200
        if len(names) > 2:
246
201
            file_id = names[2]
247
 
        return content_kind.decode('ascii'), revision_id, file_id
 
202
        return content_kind, revision_id, file_id
248
203
 
249
204
    def iter_records(self):
250
205
        """Iterate through bundle records
258
213
                raise errors.BadBundle('Record has %d names instead of 1'
259
214
                                       % len(names))
260
215
            metadata = bencode.bdecode(bytes)
261
 
            if metadata[b'storage_kind'] == b'header':
 
216
            if metadata['storage_kind'] == 'header':
262
217
                bytes = None
263
218
            else:
264
 
                _unused, bytes = next(iterator)
 
219
                _unused, bytes = iterator.next()
265
220
            yield (bytes, metadata) + self.decode_name(names[0][0])
266
221
 
267
222
 
268
223
class BundleSerializerV4(bundle_serializer.BundleSerializer):
269
224
    """Implement the high-level bundle interface"""
270
225
 
 
226
    def write(self, repository, revision_ids, forced_bases, fileobj):
 
227
        """Write a bundle to a file-like object
 
228
 
 
229
        For backwards-compatibility only
 
230
        """
 
231
        write_op = BundleWriteOperation.from_old_args(repository, revision_ids,
 
232
                                                      forced_bases, fileobj)
 
233
        return write_op.do_write()
 
234
 
271
235
    def write_bundle(self, repository, target, base, fileobj):
272
236
        """Write a bundle to a file object
273
237
 
277
241
            at.
278
242
        :param fileobj: The file-like object to write to
279
243
        """
280
 
        write_op = BundleWriteOperation(base, target, repository, fileobj)
 
244
        write_op =  BundleWriteOperation(base, target, repository, fileobj)
281
245
        return write_op.do_write()
282
246
 
283
247
    def read(self, file):
288
252
    @staticmethod
289
253
    def get_source_serializer(info):
290
254
        """Retrieve the serializer for a given info object"""
291
 
        return serializer.format_registry.get(info[b'serializer'].decode('ascii'))
 
255
        return serializer.format_registry.get(info['serializer'])
292
256
 
293
257
 
294
258
class BundleWriteOperation(object):
295
259
    """Perform the operation of writing revisions to a bundle"""
296
260
 
 
261
    @classmethod
 
262
    def from_old_args(cls, repository, revision_ids, forced_bases, fileobj):
 
263
        """Create a BundleWriteOperation from old-style arguments"""
 
264
        base, target = cls.get_base_target(revision_ids, forced_bases,
 
265
                                           repository)
 
266
        return BundleWriteOperation(base, target, repository, fileobj,
 
267
                                    revision_ids)
 
268
 
297
269
    def __init__(self, base, target, repository, fileobj, revision_ids=None):
298
270
        self.base = base
299
271
        self.target = target
308
280
            # Strip ghosts
309
281
            parents = graph.get_parent_map(revision_ids)
310
282
            self.revision_ids = [r for r in revision_ids if r in parents]
311
 
        self.revision_keys = {(revid,) for revid in self.revision_ids}
 
283
        self.revision_keys = set([(revid,) for revid in self.revision_ids])
312
284
 
313
285
    def do_write(self):
314
286
        """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():
 
287
        trace.note('Bundling %d revision(s).', len(self.revision_ids))
 
288
        self.repository.lock_read()
 
289
        try:
318
290
            self.bundle.begin()
319
291
            self.write_info()
320
292
            self.write_files()
321
293
            self.write_revisions()
322
294
            self.bundle.end()
 
295
        finally:
 
296
            self.repository.unlock()
323
297
        return self.revision_ids
324
298
 
325
299
    def write_info(self):
327
301
        serializer_format = self.repository.get_serializer_format()
328
302
        supports_rich_root = {True: 1, False: 0}[
329
303
            self.repository.supports_rich_root()]
330
 
        self.bundle.add_info_record({b'serializer': serializer_format,
331
 
                                     b'supports_rich_root': supports_rich_root})
 
304
        self.bundle.add_info_record(serializer=serializer_format,
 
305
                                    supports_rich_root=supports_rich_root)
332
306
 
333
307
    def write_files(self):
334
308
        """Write bundle records for all revisions of all files"""
335
309
        text_keys = []
336
310
        altered_fileids = self.repository.fileids_altered_by_revision_ids(
337
 
            self.revision_ids)
338
 
        for file_id, revision_ids in viewitems(altered_fileids):
 
311
                self.revision_ids)
 
312
        for file_id, revision_ids in altered_fileids.iteritems():
339
313
            for revision_id in revision_ids:
340
314
                text_keys.append((file_id, revision_id))
341
315
        self._add_mp_records_keys('file', self.repository.texts, text_keys)
344
318
        """Write bundle records for all revisions and signatures"""
345
319
        inv_vf = self.repository.inventories
346
320
        topological_order = [key[-1] for key in multiparent.topo_iter_keys(
347
 
            inv_vf, self.revision_keys)]
 
321
                                inv_vf, self.revision_keys)]
348
322
        revision_order = topological_order
349
323
        if self.target is not None and self.target in self.revision_ids:
350
324
            # Make sure the target revision is always the last entry
368
342
        """Generate mpdiffs by serializing inventories.
369
343
 
370
344
        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
 
345
        the 'inventories' vf. So we use serializer.write_inventory_to_string to
372
346
        get a 'full' representation of the tree shape, and then generate
373
347
        mpdiffs on that data stream. This stream can then be reconstructed on
374
348
        the other side.
375
349
        """
376
350
        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())
 
351
        parent_map = self.repository.inventories.get_parent_map(
 
352
                            inventory_key_order)
 
353
        missing_keys = set(inventory_key_order).difference(parent_map)
 
354
        if missing_keys:
 
355
            raise errors.RevisionNotPresent(list(missing_keys)[0],
 
356
                                            self.repository.inventories)
 
357
        inv_to_str = self.repository._serializer.write_inventory_to_string
 
358
        # Make sure that we grab the parent texts first
 
359
        just_parents = set()
 
360
        map(just_parents.update, parent_map.itervalues())
 
361
        just_parents.difference_update(parent_map)
 
362
        # Ignore ghost parents
 
363
        present_parents = self.repository.inventories.get_parent_map(
 
364
                            just_parents)
 
365
        ghost_keys = just_parents.difference(present_parents)
 
366
        needed_inventories = list(present_parents) + inventory_key_order
 
367
        needed_inventories = [k[-1] for k in needed_inventories]
 
368
        all_lines = {}
 
369
        for inv in self.repository.iter_inventories(needed_inventories):
 
370
            revision_id = inv.revision_id
 
371
            key = (revision_id,)
 
372
            as_bytes = inv_to_str(inv)
 
373
            # The sha1 is validated as the xml/textual form, not as the
 
374
            # form-in-the-repository
 
375
            sha1 = osutils.sha_string(as_bytes)
 
376
            as_lines = osutils.split_lines(as_bytes)
 
377
            del as_bytes
 
378
            all_lines[key] = as_lines
 
379
            if key in just_parents:
 
380
                # We don't transmit those entries
 
381
                continue
 
382
            # Create an mpdiff for this text, and add it to the output
 
383
            parent_keys = parent_map[key]
 
384
            # See the comment in VF.make_mpdiffs about how this effects
 
385
            # ordering when there are ghosts present. I think we have a latent
 
386
            # bug
 
387
            parent_lines = [all_lines[p_key] for p_key in parent_keys
 
388
                            if p_key not in ghost_keys]
 
389
            diff = multiparent.MultiParent.from_lines(
 
390
                as_lines, parent_lines)
 
391
            text = ''.join(diff.to_patch())
 
392
            parent_ids = [k[-1] for k in parent_keys]
381
393
            self.bundle.add_multiparent_record(text, sha1, parent_ids,
382
394
                                               'inventory', revision_id, None)
383
395
 
384
396
    def _add_revision_texts(self, revision_order):
385
397
        parent_map = self.repository.get_parent_map(revision_order)
386
 
        revision_to_bytes = self.repository._serializer.write_revision_to_string
 
398
        revision_to_str = self.repository._serializer.write_revision_to_string
387
399
        revisions = self.repository.get_revisions(revision_order)
388
400
        for revision in revisions:
389
401
            revision_id = revision.revision_id
390
402
            parents = parent_map.get(revision_id, None)
391
 
            revision_text = revision_to_bytes(revision)
 
403
            revision_text = revision_to_str(revision)
392
404
            self.bundle.add_fulltext_record(revision_text, parents,
393
 
                                            'revision', revision_id)
 
405
                                       'revision', revision_id)
394
406
            try:
395
407
                self.bundle.add_fulltext_record(
396
408
                    self.repository.get_signature_text(
397
 
                        revision_id), parents, 'signature', revision_id)
 
409
                    revision_id), parents, 'signature', revision_id)
398
410
            except errors.NoSuchRevision:
399
411
                pass
400
412
 
422
434
        for mpdiff, item_key, in zip(mpdiffs, ordered_keys):
423
435
            sha1 = sha1s[item_key]
424
436
            parents = [key[-1] for key in parent_map[item_key]]
425
 
            text = b''.join(mpdiff.to_patch())
 
437
            text = ''.join(mpdiff.to_patch())
426
438
            # Infer file id records as appropriate.
427
439
            if len(item_key) == 2:
428
440
                file_id = item_key[0]
435
447
class BundleInfoV4(object):
436
448
 
437
449
    """Provide (most of) the BundleInfo interface"""
438
 
 
439
450
    def __init__(self, fileobj, serializer):
440
451
        self._fileobj = fileobj
441
452
        self._serializer = serializer
453
464
            all into memory at once.  Reading it into memory all at once is
454
465
            (currently) faster.
455
466
        """
456
 
        with repository.lock_write():
 
467
        repository.lock_write()
 
468
        try:
457
469
            ri = RevisionInstaller(self.get_bundle_reader(stream_input),
458
470
                                   self._serializer, repository)
459
471
            return ri.install()
 
472
        finally:
 
473
            repository.unlock()
460
474
 
461
475
    def get_merge_request(self, target_repo):
462
476
        """Provide data for performing a merge
480
494
            self.__real_revisions = []
481
495
            bundle_reader = self.get_bundle_reader()
482
496
            for bytes, metadata, repo_kind, revision_id, file_id in \
483
 
                    bundle_reader.iter_records():
 
497
                bundle_reader.iter_records():
484
498
                if repo_kind == 'info':
485
499
                    serializer =\
486
500
                        self._serializer.get_source_serializer(metadata)
520
534
 
521
535
        Must be called with the Repository locked.
522
536
        """
523
 
        with _mod_repository.WriteGroup(self._repository):
524
 
            return self._install_in_write_group()
 
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
525
545
 
526
546
    def _install_in_write_group(self):
527
547
        current_file = None
532
552
        added_inv = set()
533
553
        target_revision = None
534
554
        for bytes, metadata, repo_kind, revision_id, file_id in\
535
 
                self._container.iter_records():
 
555
            self._container.iter_records():
536
556
            if repo_kind == 'info':
537
557
                if self._info is not None:
538
558
                    raise AssertionError()
539
559
                self._handle_info(metadata)
540
560
            if (pending_file_records and
541
 
                    (repo_kind, file_id) != ('file', current_file)):
 
561
                (repo_kind, file_id) != ('file', current_file)):
542
562
                # Flush the data for a single file - prevents memory
543
563
                # spiking due to buffering all files in memory.
544
564
                self._install_mp_records_keys(self._repository.texts,
545
 
                                              pending_file_records)
 
565
                    pending_file_records)
546
566
                current_file = None
547
567
                del pending_file_records[:]
548
568
            if len(pending_inventory_records) > 0 and repo_kind != 'inventory':
549
569
                self._install_inventory_records(pending_inventory_records)
550
570
                pending_inventory_records = []
551
571
            if repo_kind == 'inventory':
552
 
                pending_inventory_records.append(
553
 
                    ((revision_id,), metadata, bytes))
 
572
                pending_inventory_records.append(((revision_id,), metadata, bytes))
554
573
            if repo_kind == 'revision':
555
574
                target_revision = revision_id
556
575
                self._install_revision(revision_id, metadata, bytes)
558
577
                self._install_signature(revision_id, metadata, bytes)
559
578
            if repo_kind == 'file':
560
579
                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)
 
580
                pending_file_records.append(((file_id, revision_id), metadata, bytes))
 
581
        self._install_mp_records_keys(self._repository.texts, pending_file_records)
565
582
        return target_revision
566
583
 
567
584
    def _handle_info(self, info):
568
585
        """Extract data from an info record"""
569
586
        self._info = info
570
587
        self._source_serializer = self._serializer.get_source_serializer(info)
571
 
        if (info[b'supports_rich_root'] == 0 and
572
 
                self._repository.supports_rich_root()):
 
588
        if (info['supports_rich_root'] == 0 and
 
589
            self._repository.supports_rich_root()):
573
590
            self.update_root = True
574
591
        else:
575
592
            self.update_root = False
595
612
                prefix = key[:1]
596
613
            else:
597
614
                prefix = ()
598
 
            parents = [prefix + (parent,) for parent in meta[b'parents']]
599
 
            vf_records.append((key, parents, meta[b'sha1'], d_func(text)))
 
615
            parents = [prefix + (parent,) for parent in meta['parents']]
 
616
            vf_records.append((key, parents, meta['sha1'], d_func(text)))
600
617
        versionedfile.add_mpdiffs(vf_records)
601
618
 
602
619
    def _get_parent_inventory_texts(self, inventory_text_cache,
618
635
            # installed yet.)
619
636
            parent_keys = [(r,) for r in remaining_parent_ids]
620
637
            present_parent_map = self._repository.inventories.get_parent_map(
621
 
                parent_keys)
 
638
                                        parent_keys)
622
639
            present_parent_ids = []
623
640
            ghosts = set()
624
641
            for p_id in remaining_parent_ids:
626
643
                    present_parent_ids.append(p_id)
627
644
                else:
628
645
                    ghosts.add(p_id)
629
 
            to_lines = self._source_serializer.write_inventory_to_chunks
 
646
            to_string = self._source_serializer.write_inventory_to_string
630
647
            for parent_inv in self._repository.iter_inventories(
631
 
                    present_parent_ids):
632
 
                p_text = b''.join(to_lines(parent_inv))
 
648
                                    present_parent_ids):
 
649
                p_text = to_string(parent_inv)
633
650
                inventory_cache[parent_inv.revision_id] = parent_inv
634
651
                cached_parent_texts[parent_inv.revision_id] = p_text
635
652
                inventory_text_cache[parent_inv.revision_id] = p_text
636
653
 
637
654
        parent_texts = [cached_parent_texts[parent_id]
638
655
                        for parent_id in parent_ids
639
 
                        if parent_id not in ghosts]
 
656
                         if parent_id not in ghosts]
640
657
        return parent_texts
641
658
 
642
659
    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):
 
660
        if (self._info['serializer'] == self._repository._serializer.format_num
 
661
            and self._repository._serializer.support_altered_by_hack):
645
662
            return self._install_mp_records_keys(self._repository.inventories,
646
 
                                                 records)
 
663
                records)
647
664
        # Use a 10MB text cache, since these are string xml inventories. Note
648
665
        # that 10MB is fairly small for large projects (a single inventory can
649
666
        # be >5MB). Another possibility is to cache 10-20 inventory texts
650
667
        # instead
651
 
        inventory_text_cache = lru_cache.LRUSizeCache(10 * 1024 * 1024)
 
668
        inventory_text_cache = lru_cache.LRUSizeCache(10*1024*1024)
652
669
        # Also cache the in-memory representation. This allows us to create
653
670
        # inventory deltas to apply rather than calling add_inventory from
654
671
        # scratch each time.
655
672
        inventory_cache = lru_cache.LRUCache(10)
656
 
        with ui.ui_factory.nested_progress_bar() as pb:
 
673
        pb = ui.ui_factory.nested_progress_bar()
 
674
        try:
657
675
            num_records = len(records)
658
676
            for idx, (key, metadata, bytes) in enumerate(records):
659
677
                pb.update('installing inventory', idx, num_records)
660
678
                revision_id = key[-1]
661
 
                parent_ids = metadata[b'parents']
 
679
                parent_ids = metadata['parents']
662
680
                # Note: This assumes the local ghosts are identical to the
663
681
                #       ghosts in the source, as the Bundle serialization
664
682
                #       format doesn't record ghosts.
669
687
                # it would have to cast to a list of lines, which we get back
670
688
                # as lines and then cast back to a string.
671
689
                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']:
 
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']:
675
695
                    raise errors.BadBundle("Can't convert to target format")
676
696
                # 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
 
697
                inventory_text_cache[revision_id] = inv_text
 
698
                target_inv = self._source_serializer.read_inventory_from_string(
 
699
                    inv_text)
681
700
                self._handle_root(target_inv, parent_ids)
682
701
                parent_inv = None
683
702
                if parent_ids:
689
708
                    else:
690
709
                        delta = target_inv._make_delta(parent_inv)
691
710
                        self._repository.add_inventory_by_delta(parent_ids[0],
692
 
                                                                delta, revision_id, parent_ids)
 
711
                            delta, revision_id, parent_ids)
693
712
                except errors.UnsupportedInventoryKind:
694
713
                    raise errors.IncompatibleRevision(repr(self._repository))
695
714
                inventory_cache[revision_id] = target_inv
 
715
        finally:
 
716
            pb.finished()
696
717
 
697
718
    def _handle_root(self, target_inv, parent_ids):
698
719
        revision_id = target_inv.revision_id
699
720
        if self.update_root:
700
721
            text_key = (target_inv.root.file_id, revision_id)
701
722
            parent_keys = [(target_inv.root.file_id, parent) for
702
 
                           parent in parent_ids]
 
723
                parent in parent_ids]
703
724
            self._repository.texts.add_lines(text_key, parent_keys, [])
704
725
        elif not self._repository.supports_rich_root():
705
726
            if target_inv.root.revision != revision_id: