~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Jelmer Vernooij
  • Date: 2009-04-10 15:58:09 UTC
  • mto: This revision was merged to the branch mainline in revision 4284.
  • Revision ID: jelmer@samba.org-20090410155809-kdibzcjvp7pdb83f
Fix missing import.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
    diff,
23
23
    errors,
24
24
    iterablefile,
25
 
    lru_cache,
26
25
    multiparent,
27
26
    osutils,
28
27
    pack,
29
28
    revision as _mod_revision,
 
29
    trace,
30
30
    serializer,
31
 
    trace,
32
 
    ui,
33
31
    )
34
32
from bzrlib.bundle import bundle_data, serializer as bundle_serializer
35
 
from bzrlib import bencode
 
33
from bzrlib.util import bencode
36
34
 
37
35
 
38
36
class BundleWriter(object):
317
315
    def write_revisions(self):
318
316
        """Write bundle records for all revisions and signatures"""
319
317
        inv_vf = self.repository.inventories
320
 
        topological_order = [key[-1] for key in multiparent.topo_iter_keys(
321
 
                                inv_vf, self.revision_keys)]
322
 
        revision_order = topological_order
 
318
        revision_order = [key[-1] for key in multiparent.topo_iter_keys(inv_vf,
 
319
            self.revision_keys)]
323
320
        if self.target is not None and self.target in self.revision_ids:
324
 
            # Make sure the target revision is always the last entry
325
 
            revision_order = list(topological_order)
326
321
            revision_order.remove(self.target)
327
322
            revision_order.append(self.target)
328
 
        if self.repository._serializer.support_altered_by_hack:
329
 
            # Repositories that support_altered_by_hack means that
330
 
            # inventories.make_mpdiffs() contains all the data about the tree
331
 
            # shape. Formats without support_altered_by_hack require
332
 
            # chk_bytes/etc, so we use a different code path.
333
 
            self._add_mp_records_keys('inventory', inv_vf,
334
 
                                      [(revid,) for revid in topological_order])
335
 
        else:
336
 
            # Inventories should always be added in pure-topological order, so
337
 
            # that we can apply the mpdiff for the child to the parent texts.
338
 
            self._add_inventory_mpdiffs_from_serializer(topological_order)
339
 
        self._add_revision_texts(revision_order)
340
 
 
341
 
    def _add_inventory_mpdiffs_from_serializer(self, revision_order):
342
 
        """Generate mpdiffs by serializing inventories.
343
 
 
344
 
        The current repository only has part of the tree shape information in
345
 
        the 'inventories' vf. So we use serializer.write_inventory_to_string to
346
 
        get a 'full' representation of the tree shape, and then generate
347
 
        mpdiffs on that data stream. This stream can then be reconstructed on
348
 
        the other side.
349
 
        """
350
 
        inventory_key_order = [(r,) for r in revision_order]
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]
393
 
            self.bundle.add_multiparent_record(text, sha1, parent_ids,
394
 
                                               'inventory', revision_id, None)
395
 
 
396
 
    def _add_revision_texts(self, revision_order):
 
323
        self._add_mp_records_keys('inventory', inv_vf, [(revid,) for revid in revision_order])
397
324
        parent_map = self.repository.get_parent_map(revision_order)
398
325
        revision_to_str = self.repository._serializer.write_revision_to_string
399
326
        revisions = self.repository.get_revisions(revision_order)
616
543
            vf_records.append((key, parents, meta['sha1'], d_func(text)))
617
544
        versionedfile.add_mpdiffs(vf_records)
618
545
 
619
 
    def _get_parent_inventory_texts(self, inventory_text_cache,
620
 
                                    inventory_cache, parent_ids):
621
 
        cached_parent_texts = {}
622
 
        remaining_parent_ids = []
623
 
        for parent_id in parent_ids:
624
 
            p_text = inventory_text_cache.get(parent_id, None)
625
 
            if p_text is None:
626
 
                remaining_parent_ids.append(parent_id)
627
 
            else:
628
 
                cached_parent_texts[parent_id] = p_text
629
 
        ghosts = ()
630
 
        # TODO: Use inventory_cache to grab inventories we already have in
631
 
        #       memory
632
 
        if remaining_parent_ids:
633
 
            # first determine what keys are actually present in the local
634
 
            # inventories object (don't use revisions as they haven't been
635
 
            # installed yet.)
636
 
            parent_keys = [(r,) for r in remaining_parent_ids]
637
 
            present_parent_map = self._repository.inventories.get_parent_map(
638
 
                                        parent_keys)
639
 
            present_parent_ids = []
640
 
            ghosts = set()
641
 
            for p_id in remaining_parent_ids:
642
 
                if (p_id,) in present_parent_map:
643
 
                    present_parent_ids.append(p_id)
644
 
                else:
645
 
                    ghosts.add(p_id)
646
 
            to_string = self._source_serializer.write_inventory_to_string
647
 
            for parent_inv in self._repository.iter_inventories(
648
 
                                    present_parent_ids):
649
 
                p_text = to_string(parent_inv)
650
 
                inventory_cache[parent_inv.revision_id] = parent_inv
651
 
                cached_parent_texts[parent_inv.revision_id] = p_text
652
 
                inventory_text_cache[parent_inv.revision_id] = p_text
653
 
 
654
 
        parent_texts = [cached_parent_texts[parent_id]
655
 
                        for parent_id in parent_ids
656
 
                         if parent_id not in ghosts]
657
 
        return parent_texts
658
 
 
659
546
    def _install_inventory_records(self, records):
660
 
        if (self._info['serializer'] == self._repository._serializer.format_num
661
 
            and self._repository._serializer.support_altered_by_hack):
 
547
        if self._info['serializer'] == self._repository._serializer.format_num:
662
548
            return self._install_mp_records_keys(self._repository.inventories,
663
549
                records)
664
 
        # Use a 10MB text cache, since these are string xml inventories. Note
665
 
        # that 10MB is fairly small for large projects (a single inventory can
666
 
        # be >5MB). Another possibility is to cache 10-20 inventory texts
667
 
        # instead
668
 
        inventory_text_cache = lru_cache.LRUSizeCache(10*1024*1024)
669
 
        # Also cache the in-memory representation. This allows us to create
670
 
        # inventory deltas to apply rather than calling add_inventory from
671
 
        # scratch each time.
672
 
        inventory_cache = lru_cache.LRUCache(10)
673
 
        pb = ui.ui_factory.nested_progress_bar()
674
 
        try:
675
 
            num_records = len(records)
676
 
            for idx, (key, metadata, bytes) in enumerate(records):
677
 
                pb.update('installing inventory', idx, num_records)
678
 
                revision_id = key[-1]
679
 
                parent_ids = metadata['parents']
680
 
                # Note: This assumes the local ghosts are identical to the
681
 
                #       ghosts in the source, as the Bundle serialization
682
 
                #       format doesn't record ghosts.
683
 
                p_texts = self._get_parent_inventory_texts(inventory_text_cache,
684
 
                                                           inventory_cache,
685
 
                                                           parent_ids)
686
 
                # Why does to_lines() take strings as the source, it seems that
687
 
                # it would have to cast to a list of lines, which we get back
688
 
                # as lines and then cast back to a string.
689
 
                target_lines = multiparent.MultiParent.from_patch(bytes
690
 
                            ).to_lines(p_texts)
691
 
                inv_text = ''.join(target_lines)
692
 
                del target_lines
693
 
                sha1 = osutils.sha_string(inv_text)
694
 
                if sha1 != metadata['sha1']:
695
 
                    raise errors.BadBundle("Can't convert to target format")
696
 
                # Add this to the cache so we don't have to extract it again.
697
 
                inventory_text_cache[revision_id] = inv_text
698
 
                target_inv = self._source_serializer.read_inventory_from_string(
699
 
                    inv_text)
700
 
                self._handle_root(target_inv, parent_ids)
701
 
                parent_inv = None
702
 
                if parent_ids:
703
 
                    parent_inv = inventory_cache.get(parent_ids[0], None)
704
 
                try:
705
 
                    if parent_inv is None:
706
 
                        self._repository.add_inventory(revision_id, target_inv,
707
 
                                                       parent_ids)
708
 
                    else:
709
 
                        delta = target_inv._make_delta(parent_inv)
710
 
                        self._repository.add_inventory_by_delta(parent_ids[0],
711
 
                            delta, revision_id, parent_ids)
712
 
                except errors.UnsupportedInventoryKind:
713
 
                    raise errors.IncompatibleRevision(repr(self._repository))
714
 
                inventory_cache[revision_id] = target_inv
715
 
        finally:
716
 
            pb.finished()
 
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))
717
570
 
718
571
    def _handle_root(self, target_inv, parent_ids):
719
572
        revision_id = target_inv.revision_id