~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil
  • Date: 2017-01-30 14:42:05 UTC
  • mfrom: (6620.1.1 trunk)
  • Revision ID: tarmac-20170130144205-r8fh2xpmiuxyozpv
Merge  2.7 into trunk including fix for bug #1657238 [r=vila]

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
 
 
17
from __future__ import absolute_import
16
18
 
17
19
from cStringIO import StringIO
18
20
import bz2
19
21
import re
20
22
 
21
23
from bzrlib import (
22
 
    diff,
23
24
    errors,
24
25
    iterablefile,
 
26
    lru_cache,
25
27
    multiparent,
26
28
    osutils,
27
29
    pack,
28
30
    revision as _mod_revision,
 
31
    serializer,
29
32
    trace,
30
 
    xml_serializer,
 
33
    ui,
 
34
    versionedfile as _mod_versionedfile,
31
35
    )
32
 
from bzrlib.bundle import bundle_data, serializer
33
 
from bzrlib.util import bencode
 
36
from bzrlib.bundle import bundle_data, serializer as bundle_serializer
 
37
from bzrlib.i18n import ngettext
 
38
from bzrlib import bencode
 
39
 
 
40
 
 
41
class _MPDiffInventoryGenerator(_mod_versionedfile._MPDiffGenerator):
 
42
    """Generate Inventory diffs serialized inventories."""
 
43
 
 
44
    def __init__(self, repo, inventory_keys):
 
45
        super(_MPDiffInventoryGenerator, self).__init__(repo.inventories,
 
46
            inventory_keys)
 
47
        self.repo = repo
 
48
        self.sha1s = {}
 
49
 
 
50
    def iter_diffs(self):
 
51
        """Compute the diffs one at a time."""
 
52
        # This is instead of compute_diffs() since we guarantee our ordering of
 
53
        # inventories, we don't have to do any buffering
 
54
        self._find_needed_keys()
 
55
        # We actually use a slightly different ordering. We grab all of the
 
56
        # parents first, and then grab the ordered requests.
 
57
        needed_ids = [k[-1] for k in self.present_parents]
 
58
        needed_ids.extend([k[-1] for k in self.ordered_keys])
 
59
        inv_to_str = self.repo._serializer.write_inventory_to_string
 
60
        for inv in self.repo.iter_inventories(needed_ids):
 
61
            revision_id = inv.revision_id
 
62
            key = (revision_id,)
 
63
            if key in self.present_parents:
 
64
                # Not a key we will transmit, which is a shame, since because
 
65
                # of that bundles don't work with stacked branches
 
66
                parent_ids = None
 
67
            else:
 
68
                parent_ids = [k[-1] for k in self.parent_map[key]]
 
69
            as_bytes = inv_to_str(inv)
 
70
            self._process_one_record(key, (as_bytes,))
 
71
            if parent_ids is None:
 
72
                continue
 
73
            diff = self.diffs.pop(key)
 
74
            sha1 = osutils.sha_string(as_bytes)
 
75
            yield revision_id, parent_ids, sha1, diff
34
76
 
35
77
 
36
78
class BundleWriter(object):
54
96
 
55
97
    def begin(self):
56
98
        """Start writing the bundle"""
57
 
        self._fileobj.write(serializer._get_bundle_header(
58
 
            serializer.v4_string))
 
99
        self._fileobj.write(bundle_serializer._get_bundle_header(
 
100
            bundle_serializer.v4_string))
59
101
        self._fileobj.write('#\n')
60
102
        self._container.begin()
61
103
 
218
260
            yield (bytes, metadata) + self.decode_name(names[0][0])
219
261
 
220
262
 
221
 
class BundleSerializerV4(serializer.BundleSerializer):
 
263
class BundleSerializerV4(bundle_serializer.BundleSerializer):
222
264
    """Implement the high-level bundle interface"""
223
265
 
224
266
    def write(self, repository, revision_ids, forced_bases, fileobj):
250
292
    @staticmethod
251
293
    def get_source_serializer(info):
252
294
        """Retrieve the serializer for a given info object"""
253
 
        return xml_serializer.format_registry.get(info['serializer'])
 
295
        return serializer.format_registry.get(info['serializer'])
254
296
 
255
297
 
256
298
class BundleWriteOperation(object):
270
312
        self.repository = repository
271
313
        bundle = BundleWriter(fileobj)
272
314
        self.bundle = bundle
273
 
        self.base_ancestry = set(repository.get_ancestry(base,
274
 
                                                         topo_sorted=False))
275
315
        if revision_ids is not None:
276
316
            self.revision_ids = revision_ids
277
317
        else:
278
 
            revision_ids = set(repository.get_ancestry(target,
279
 
                                                       topo_sorted=False))
280
 
            self.revision_ids = revision_ids.difference(self.base_ancestry)
 
318
            graph = repository.get_graph()
 
319
            revision_ids = graph.find_unique_ancestors(target, [base])
 
320
            # Strip ghosts
 
321
            parents = graph.get_parent_map(revision_ids)
 
322
            self.revision_ids = [r for r in revision_ids if r in parents]
281
323
        self.revision_keys = set([(revid,) for revid in self.revision_ids])
282
324
 
283
325
    def do_write(self):
284
326
        """Write all data to the bundle"""
 
327
        trace.note(ngettext('Bundling %d revision.', 'Bundling %d revisions.',
 
328
                            len(self.revision_ids)), len(self.revision_ids))
285
329
        self.repository.lock_read()
286
330
        try:
287
331
            self.bundle.begin()
314
358
    def write_revisions(self):
315
359
        """Write bundle records for all revisions and signatures"""
316
360
        inv_vf = self.repository.inventories
317
 
        revision_order = [key[-1] for key in multiparent.topo_iter_keys(inv_vf,
318
 
            self.revision_keys)]
 
361
        topological_order = [key[-1] for key in multiparent.topo_iter_keys(
 
362
                                inv_vf, self.revision_keys)]
 
363
        revision_order = topological_order
319
364
        if self.target is not None and self.target in self.revision_ids:
 
365
            # Make sure the target revision is always the last entry
 
366
            revision_order = list(topological_order)
320
367
            revision_order.remove(self.target)
321
368
            revision_order.append(self.target)
322
 
        self._add_mp_records_keys('inventory', inv_vf, [(revid,) for revid in revision_order])
 
369
        if self.repository._serializer.support_altered_by_hack:
 
370
            # Repositories that support_altered_by_hack means that
 
371
            # inventories.make_mpdiffs() contains all the data about the tree
 
372
            # shape. Formats without support_altered_by_hack require
 
373
            # chk_bytes/etc, so we use a different code path.
 
374
            self._add_mp_records_keys('inventory', inv_vf,
 
375
                                      [(revid,) for revid in topological_order])
 
376
        else:
 
377
            # Inventories should always be added in pure-topological order, so
 
378
            # that we can apply the mpdiff for the child to the parent texts.
 
379
            self._add_inventory_mpdiffs_from_serializer(topological_order)
 
380
        self._add_revision_texts(revision_order)
 
381
 
 
382
    def _add_inventory_mpdiffs_from_serializer(self, revision_order):
 
383
        """Generate mpdiffs by serializing inventories.
 
384
 
 
385
        The current repository only has part of the tree shape information in
 
386
        the 'inventories' vf. So we use serializer.write_inventory_to_string to
 
387
        get a 'full' representation of the tree shape, and then generate
 
388
        mpdiffs on that data stream. This stream can then be reconstructed on
 
389
        the other side.
 
390
        """
 
391
        inventory_key_order = [(r,) for r in revision_order]
 
392
        generator = _MPDiffInventoryGenerator(self.repository,
 
393
                                              inventory_key_order)
 
394
        for revision_id, parent_ids, sha1, diff in generator.iter_diffs():
 
395
            text = ''.join(diff.to_patch())
 
396
            self.bundle.add_multiparent_record(text, sha1, parent_ids,
 
397
                                               'inventory', revision_id, None)
 
398
 
 
399
    def _add_revision_texts(self, revision_order):
323
400
        parent_map = self.repository.get_parent_map(revision_order)
324
 
        for revision_id in revision_order:
 
401
        revision_to_str = self.repository._serializer.write_revision_to_string
 
402
        revisions = self.repository.get_revisions(revision_order)
 
403
        for revision in revisions:
 
404
            revision_id = revision.revision_id
325
405
            parents = parent_map.get(revision_id, None)
326
 
            revision_text = self.repository.get_revision_xml(revision_id)
 
406
            revision_text = revision_to_str(revision)
327
407
            self.bundle.add_fulltext_record(revision_text, parents,
328
408
                                       'revision', revision_id)
329
409
            try:
454
534
 
455
535
    def install(self):
456
536
        """Perform the installation.
457
 
        
 
537
 
458
538
        Must be called with the Repository locked.
459
539
        """
460
540
        self._repository.start_write_group()
539
619
            vf_records.append((key, parents, meta['sha1'], d_func(text)))
540
620
        versionedfile.add_mpdiffs(vf_records)
541
621
 
 
622
    def _get_parent_inventory_texts(self, inventory_text_cache,
 
623
                                    inventory_cache, parent_ids):
 
624
        cached_parent_texts = {}
 
625
        remaining_parent_ids = []
 
626
        for parent_id in parent_ids:
 
627
            p_text = inventory_text_cache.get(parent_id, None)
 
628
            if p_text is None:
 
629
                remaining_parent_ids.append(parent_id)
 
630
            else:
 
631
                cached_parent_texts[parent_id] = p_text
 
632
        ghosts = ()
 
633
        # TODO: Use inventory_cache to grab inventories we already have in
 
634
        #       memory
 
635
        if remaining_parent_ids:
 
636
            # first determine what keys are actually present in the local
 
637
            # inventories object (don't use revisions as they haven't been
 
638
            # installed yet.)
 
639
            parent_keys = [(r,) for r in remaining_parent_ids]
 
640
            present_parent_map = self._repository.inventories.get_parent_map(
 
641
                                        parent_keys)
 
642
            present_parent_ids = []
 
643
            ghosts = set()
 
644
            for p_id in remaining_parent_ids:
 
645
                if (p_id,) in present_parent_map:
 
646
                    present_parent_ids.append(p_id)
 
647
                else:
 
648
                    ghosts.add(p_id)
 
649
            to_string = self._source_serializer.write_inventory_to_string
 
650
            for parent_inv in self._repository.iter_inventories(
 
651
                                    present_parent_ids):
 
652
                p_text = to_string(parent_inv)
 
653
                inventory_cache[parent_inv.revision_id] = parent_inv
 
654
                cached_parent_texts[parent_inv.revision_id] = p_text
 
655
                inventory_text_cache[parent_inv.revision_id] = p_text
 
656
 
 
657
        parent_texts = [cached_parent_texts[parent_id]
 
658
                        for parent_id in parent_ids
 
659
                         if parent_id not in ghosts]
 
660
        return parent_texts
 
661
 
542
662
    def _install_inventory_records(self, records):
543
 
        if self._info['serializer'] == self._repository._serializer.format_num:
 
663
        if (self._info['serializer'] == self._repository._serializer.format_num
 
664
            and self._repository._serializer.support_altered_by_hack):
544
665
            return self._install_mp_records_keys(self._repository.inventories,
545
666
                records)
546
 
        for key, metadata, bytes in records:
547
 
            revision_id = key[-1]
548
 
            parent_ids = metadata['parents']
549
 
            parents = [self._repository.get_inventory(p)
550
 
                       for p in parent_ids]
551
 
            p_texts = [self._source_serializer.write_inventory_to_string(p)
552
 
                       for p in parents]
553
 
            target_lines = multiparent.MultiParent.from_patch(bytes).to_lines(
554
 
                p_texts)
555
 
            sha1 = osutils.sha_strings(target_lines)
556
 
            if sha1 != metadata['sha1']:
557
 
                raise errors.BadBundle("Can't convert to target format")
558
 
            target_inv = self._source_serializer.read_inventory_from_string(
559
 
                ''.join(target_lines))
560
 
            self._handle_root(target_inv, parent_ids)
561
 
            try:
562
 
                self._repository.add_inventory(revision_id, target_inv,
563
 
                                               parent_ids)
564
 
            except errors.UnsupportedInventoryKind:
565
 
                raise errors.IncompatibleRevision(repr(self._repository))
 
667
        # Use a 10MB text cache, since these are string xml inventories. Note
 
668
        # that 10MB is fairly small for large projects (a single inventory can
 
669
        # be >5MB). Another possibility is to cache 10-20 inventory texts
 
670
        # instead
 
671
        inventory_text_cache = lru_cache.LRUSizeCache(10*1024*1024)
 
672
        # Also cache the in-memory representation. This allows us to create
 
673
        # inventory deltas to apply rather than calling add_inventory from
 
674
        # scratch each time.
 
675
        inventory_cache = lru_cache.LRUCache(10)
 
676
        pb = ui.ui_factory.nested_progress_bar()
 
677
        try:
 
678
            num_records = len(records)
 
679
            for idx, (key, metadata, bytes) in enumerate(records):
 
680
                pb.update('installing inventory', idx, num_records)
 
681
                revision_id = key[-1]
 
682
                parent_ids = metadata['parents']
 
683
                # Note: This assumes the local ghosts are identical to the
 
684
                #       ghosts in the source, as the Bundle serialization
 
685
                #       format doesn't record ghosts.
 
686
                p_texts = self._get_parent_inventory_texts(inventory_text_cache,
 
687
                                                           inventory_cache,
 
688
                                                           parent_ids)
 
689
                # Why does to_lines() take strings as the source, it seems that
 
690
                # it would have to cast to a list of lines, which we get back
 
691
                # as lines and then cast back to a string.
 
692
                target_lines = multiparent.MultiParent.from_patch(bytes
 
693
                            ).to_lines(p_texts)
 
694
                inv_text = ''.join(target_lines)
 
695
                del target_lines
 
696
                sha1 = osutils.sha_string(inv_text)
 
697
                if sha1 != metadata['sha1']:
 
698
                    raise errors.BadBundle("Can't convert to target format")
 
699
                # Add this to the cache so we don't have to extract it again.
 
700
                inventory_text_cache[revision_id] = inv_text
 
701
                target_inv = self._source_serializer.read_inventory_from_string(
 
702
                    inv_text)
 
703
                self._handle_root(target_inv, parent_ids)
 
704
                parent_inv = None
 
705
                if parent_ids:
 
706
                    parent_inv = inventory_cache.get(parent_ids[0], None)
 
707
                try:
 
708
                    if parent_inv is None:
 
709
                        self._repository.add_inventory(revision_id, target_inv,
 
710
                                                       parent_ids)
 
711
                    else:
 
712
                        delta = target_inv._make_delta(parent_inv)
 
713
                        self._repository.add_inventory_by_delta(parent_ids[0],
 
714
                            delta, revision_id, parent_ids)
 
715
                except errors.UnsupportedInventoryKind:
 
716
                    raise errors.IncompatibleRevision(repr(self._repository))
 
717
                inventory_cache[revision_id] = target_inv
 
718
        finally:
 
719
            pb.finished()
566
720
 
567
721
    def _handle_root(self, target_inv, parent_ids):
568
722
        revision_id = target_inv.revision_id