~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

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
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
from __future__ import absolute_import
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
16
 
19
17
from cStringIO import StringIO
20
18
import bz2
21
19
import re
22
20
 
23
21
from bzrlib import (
 
22
    diff,
24
23
    errors,
25
24
    iterablefile,
26
 
    lru_cache,
27
25
    multiparent,
28
26
    osutils,
29
27
    pack,
30
28
    revision as _mod_revision,
31
 
    serializer,
32
29
    trace,
33
 
    ui,
34
 
    versionedfile as _mod_versionedfile,
 
30
    xml_serializer,
35
31
    )
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
 
32
from bzrlib.bundle import bundle_data, serializer
 
33
from bzrlib.util import bencode
76
34
 
77
35
 
78
36
class BundleWriter(object):
96
54
 
97
55
    def begin(self):
98
56
        """Start writing the bundle"""
99
 
        self._fileobj.write(bundle_serializer._get_bundle_header(
100
 
            bundle_serializer.v4_string))
 
57
        self._fileobj.write(serializer._get_bundle_header(
 
58
            serializer.v4_string))
101
59
        self._fileobj.write('#\n')
102
60
        self._container.begin()
103
61
 
260
218
            yield (bytes, metadata) + self.decode_name(names[0][0])
261
219
 
262
220
 
263
 
class BundleSerializerV4(bundle_serializer.BundleSerializer):
 
221
class BundleSerializerV4(serializer.BundleSerializer):
264
222
    """Implement the high-level bundle interface"""
265
223
 
266
224
    def write(self, repository, revision_ids, forced_bases, fileobj):
292
250
    @staticmethod
293
251
    def get_source_serializer(info):
294
252
        """Retrieve the serializer for a given info object"""
295
 
        return serializer.format_registry.get(info['serializer'])
 
253
        return xml_serializer.format_registry.get(info['serializer'])
296
254
 
297
255
 
298
256
class BundleWriteOperation(object):
312
270
        self.repository = repository
313
271
        bundle = BundleWriter(fileobj)
314
272
        self.bundle = bundle
 
273
        self.base_ancestry = set(repository.get_ancestry(base,
 
274
                                                         topo_sorted=False))
315
275
        if revision_ids is not None:
316
276
            self.revision_ids = revision_ids
317
277
        else:
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]
 
278
            revision_ids = set(repository.get_ancestry(target,
 
279
                                                       topo_sorted=False))
 
280
            self.revision_ids = revision_ids.difference(self.base_ancestry)
323
281
        self.revision_keys = set([(revid,) for revid in self.revision_ids])
324
282
 
325
283
    def do_write(self):
326
284
        """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))
329
285
        self.repository.lock_read()
330
286
        try:
331
287
            self.bundle.begin()
358
314
    def write_revisions(self):
359
315
        """Write bundle records for all revisions and signatures"""
360
316
        inv_vf = self.repository.inventories
361
 
        topological_order = [key[-1] for key in multiparent.topo_iter_keys(
362
 
                                inv_vf, self.revision_keys)]
363
 
        revision_order = topological_order
 
317
        revision_order = [key[-1] for key in multiparent.topo_iter_keys(inv_vf,
 
318
            self.revision_keys)]
364
319
        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)
367
320
            revision_order.remove(self.target)
368
321
            revision_order.append(self.target)
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):
 
322
        self._add_mp_records_keys('inventory', inv_vf, [(revid,) for revid in revision_order])
400
323
        parent_map = self.repository.get_parent_map(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
 
324
        for revision_id in revision_order:
405
325
            parents = parent_map.get(revision_id, None)
406
 
            revision_text = revision_to_str(revision)
 
326
            revision_text = self.repository.get_revision_xml(revision_id)
407
327
            self.bundle.add_fulltext_record(revision_text, parents,
408
328
                                       'revision', revision_id)
409
329
            try:
534
454
 
535
455
    def install(self):
536
456
        """Perform the installation.
537
 
 
 
457
        
538
458
        Must be called with the Repository locked.
539
459
        """
540
460
        self._repository.start_write_group()
619
539
            vf_records.append((key, parents, meta['sha1'], d_func(text)))
620
540
        versionedfile.add_mpdiffs(vf_records)
621
541
 
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
 
 
662
542
    def _install_inventory_records(self, records):
663
 
        if (self._info['serializer'] == self._repository._serializer.format_num
664
 
            and self._repository._serializer.support_altered_by_hack):
 
543
        if self._info['serializer'] == self._repository._serializer.format_num:
665
544
            return self._install_mp_records_keys(self._repository.inventories,
666
545
                records)
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()
 
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))
720
566
 
721
567
    def _handle_root(self, target_inv, parent_ids):
722
568
        revision_id = target_inv.revision_id