~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Andrew Bennetts
  • Date: 2009-07-27 05:35:00 UTC
  • mfrom: (4570 +trunk)
  • mto: (4634.6.29 2.0)
  • mto: This revision was merged to the branch mainline in revision 4680.
  • Revision ID: andrew.bennetts@canonical.com-20090727053500-q76zsn2dx33jhmj5
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008, 2009 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
16
 
17
17
from bzrlib.lazy_import import lazy_import
18
18
lazy_import(globals(), """
23
23
from bzrlib import (
24
24
    bzrdir,
25
25
    check,
 
26
    chk_map,
26
27
    debug,
27
28
    errors,
28
29
    fifo_cache,
29
30
    generate_ids,
30
31
    gpg,
31
32
    graph,
 
33
    inventory,
32
34
    lazy_regex,
33
35
    lockable_files,
34
36
    lockdir,
35
37
    lru_cache,
36
38
    osutils,
37
 
    remote,
38
39
    revision as _mod_revision,
39
40
    symbol_versioning,
40
41
    tsort,
49
50
 
50
51
from bzrlib.decorators import needs_read_lock, needs_write_lock
51
52
from bzrlib.inter import InterObject
52
 
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
 
53
from bzrlib.inventory import (
 
54
    Inventory,
 
55
    InventoryDirectory,
 
56
    ROOT_ID,
 
57
    entry_factory,
 
58
    )
53
59
from bzrlib import registry
54
 
from bzrlib.symbol_versioning import (
55
 
        deprecated_method,
56
 
        one_one,
57
 
        one_two,
58
 
        one_six,
59
 
        )
60
60
from bzrlib.trace import (
61
61
    log_exception_quietly, note, mutter, mutter_callsite, warning)
62
62
 
126
126
        # valid. Callers that will call record_delete() should call
127
127
        # .will_record_deletes() to indicate that.
128
128
        self._recording_deletes = False
 
129
        # memo'd check for no-op commits.
 
130
        self._any_changes = False
 
131
 
 
132
    def any_changes(self):
 
133
        """Return True if any entries were changed.
 
134
        
 
135
        This includes merge-only changes. It is the core for the --unchanged
 
136
        detection in commit.
 
137
 
 
138
        :return: True if any changes have occured.
 
139
        """
 
140
        return self._any_changes
129
141
 
130
142
    def _validate_unicode_text(self, text, context):
131
143
        """Verify things like commit messages don't have bogus characters."""
176
188
        deserializing the inventory, while we already have a copy in
177
189
        memory.
178
190
        """
 
191
        if self.new_inventory is None:
 
192
            self.new_inventory = self.repository.get_inventory(
 
193
                self._new_revision_id)
179
194
        return RevisionTree(self.repository, self.new_inventory,
180
 
                            self._new_revision_id)
 
195
            self._new_revision_id)
181
196
 
182
197
    def finish_inventory(self):
183
 
        """Tell the builder that the inventory is finished."""
184
 
        if self.new_inventory.root is None:
185
 
            raise AssertionError('Root entry should be supplied to'
186
 
                ' record_entry_contents, as of bzr 0.10.')
187
 
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
188
 
        self.new_inventory.revision_id = self._new_revision_id
189
 
        self.inv_sha1 = self.repository.add_inventory(
190
 
            self._new_revision_id,
191
 
            self.new_inventory,
192
 
            self.parents
193
 
            )
 
198
        """Tell the builder that the inventory is finished.
 
199
 
 
200
        :return: The inventory id in the repository, which can be used with
 
201
            repository.get_inventory.
 
202
        """
 
203
        if self.new_inventory is None:
 
204
            # an inventory delta was accumulated without creating a new
 
205
            # inventory.
 
206
            basis_id = self.basis_delta_revision
 
207
            self.inv_sha1 = self.repository.add_inventory_by_delta(
 
208
                basis_id, self._basis_delta, self._new_revision_id,
 
209
                self.parents)
 
210
        else:
 
211
            if self.new_inventory.root is None:
 
212
                raise AssertionError('Root entry should be supplied to'
 
213
                    ' record_entry_contents, as of bzr 0.10.')
 
214
                self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
 
215
            self.new_inventory.revision_id = self._new_revision_id
 
216
            self.inv_sha1 = self.repository.add_inventory(
 
217
                self._new_revision_id,
 
218
                self.new_inventory,
 
219
                self.parents
 
220
                )
 
221
        return self._new_revision_id
194
222
 
195
223
    def _gen_revision_id(self):
196
224
        """Return new revision-id."""
233
261
        # _new_revision_id
234
262
        ie.revision = self._new_revision_id
235
263
 
 
264
    def _require_root_change(self, tree):
 
265
        """Enforce an appropriate root object change.
 
266
 
 
267
        This is called once when record_iter_changes is called, if and only if
 
268
        the root was not in the delta calculated by record_iter_changes.
 
269
 
 
270
        :param tree: The tree which is being committed.
 
271
        """
 
272
        # NB: if there are no parents then this method is not called, so no
 
273
        # need to guard on parents having length.
 
274
        entry = entry_factory['directory'](tree.path2id(''), '',
 
275
            None)
 
276
        entry.revision = self._new_revision_id
 
277
        self._basis_delta.append(('', '', entry.file_id, entry))
 
278
 
236
279
    def _get_delta(self, ie, basis_inv, path):
237
280
        """Get a delta against the basis inventory for ie."""
238
281
        if ie.file_id not in basis_inv:
280
323
            raise AssertionError("recording deletes not activated.")
281
324
        delta = (path, None, file_id, None)
282
325
        self._basis_delta.append(delta)
 
326
        self._any_changes = True
283
327
        return delta
284
328
 
285
329
    def will_record_deletes(self):
290
334
        builder.record_delete().
291
335
        """
292
336
        self._recording_deletes = True
 
337
        try:
 
338
            basis_id = self.parents[0]
 
339
        except IndexError:
 
340
            basis_id = _mod_revision.NULL_REVISION
 
341
        self.basis_delta_revision = basis_id
293
342
 
294
343
    def record_entry_contents(self, ie, parent_invs, path, tree,
295
344
        content_summary):
442
491
            ie.executable = content_summary[2]
443
492
            file_obj, stat_value = tree.get_file_with_stat(ie.file_id, path)
444
493
            try:
445
 
                lines = file_obj.readlines()
 
494
                text = file_obj.read()
446
495
            finally:
447
496
                file_obj.close()
448
497
            try:
449
498
                ie.text_sha1, ie.text_size = self._add_text_to_weave(
450
 
                    ie.file_id, lines, heads, nostore_sha)
 
499
                    ie.file_id, text, heads, nostore_sha)
451
500
                # Let the caller know we generated a stat fingerprint.
452
501
                fingerprint = (ie.text_sha1, stat_value)
453
502
            except errors.ExistingContent:
465
514
                # carry over:
466
515
                ie.revision = parent_entry.revision
467
516
                return self._get_delta(ie, basis_inv, path), False, None
468
 
            lines = []
469
 
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
517
            self._add_text_to_weave(ie.file_id, '', heads, None)
470
518
        elif kind == 'symlink':
471
519
            current_link_target = content_summary[3]
472
520
            if not store:
480
528
                ie.symlink_target = parent_entry.symlink_target
481
529
                return self._get_delta(ie, basis_inv, path), False, None
482
530
            ie.symlink_target = current_link_target
483
 
            lines = []
484
 
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
531
            self._add_text_to_weave(ie.file_id, '', heads, None)
485
532
        elif kind == 'tree-reference':
486
533
            if not store:
487
534
                if content_summary[3] != parent_entry.reference_revision:
492
539
                ie.revision = parent_entry.revision
493
540
                return self._get_delta(ie, basis_inv, path), False, None
494
541
            ie.reference_revision = content_summary[3]
495
 
            lines = []
496
 
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
542
            self._add_text_to_weave(ie.file_id, '', heads, None)
497
543
        else:
498
544
            raise NotImplementedError('unknown kind')
499
545
        ie.revision = self._new_revision_id
 
546
        self._any_changes = True
500
547
        return self._get_delta(ie, basis_inv, path), True, fingerprint
501
548
 
502
 
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
503
 
        # Note: as we read the content directly from the tree, we know its not
504
 
        # been turned into unicode or badly split - but a broken tree
505
 
        # implementation could give us bad output from readlines() so this is
506
 
        # not a guarantee of safety. What would be better is always checking
507
 
        # the content during test suite execution. RBC 20070912
508
 
        parent_keys = tuple((file_id, parent) for parent in parents)
509
 
        return self.repository.texts.add_lines(
510
 
            (file_id, self._new_revision_id), parent_keys, new_lines,
511
 
            nostore_sha=nostore_sha, random_id=self.random_revid,
512
 
            check_content=False)[0:2]
 
549
    def record_iter_changes(self, tree, basis_revision_id, iter_changes,
 
550
        _entry_factory=entry_factory):
 
551
        """Record a new tree via iter_changes.
 
552
 
 
553
        :param tree: The tree to obtain text contents from for changed objects.
 
554
        :param basis_revision_id: The revision id of the tree the iter_changes
 
555
            has been generated against. Currently assumed to be the same
 
556
            as self.parents[0] - if it is not, errors may occur.
 
557
        :param iter_changes: An iter_changes iterator with the changes to apply
 
558
            to basis_revision_id. The iterator must not include any items with
 
559
            a current kind of None - missing items must be either filtered out
 
560
            or errored-on beefore record_iter_changes sees the item.
 
561
        :param _entry_factory: Private method to bind entry_factory locally for
 
562
            performance.
 
563
        :return: A generator of (file_id, relpath, fs_hash) tuples for use with
 
564
            tree._observed_sha1.
 
565
        """
 
566
        # Create an inventory delta based on deltas between all the parents and
 
567
        # deltas between all the parent inventories. We use inventory delta's 
 
568
        # between the inventory objects because iter_changes masks
 
569
        # last-changed-field only changes.
 
570
        # Working data:
 
571
        # file_id -> change map, change is fileid, paths, changed, versioneds,
 
572
        # parents, names, kinds, executables
 
573
        merged_ids = {}
 
574
        # {file_id -> revision_id -> inventory entry, for entries in parent
 
575
        # trees that are not parents[0]
 
576
        parent_entries = {}
 
577
        ghost_basis = False
 
578
        try:
 
579
            revtrees = list(self.repository.revision_trees(self.parents))
 
580
        except errors.NoSuchRevision:
 
581
            # one or more ghosts, slow path.
 
582
            revtrees = []
 
583
            for revision_id in self.parents:
 
584
                try:
 
585
                    revtrees.append(self.repository.revision_tree(revision_id))
 
586
                except errors.NoSuchRevision:
 
587
                    if not revtrees:
 
588
                        basis_revision_id = _mod_revision.NULL_REVISION
 
589
                        ghost_basis = True
 
590
                    revtrees.append(self.repository.revision_tree(
 
591
                        _mod_revision.NULL_REVISION))
 
592
        # The basis inventory from a repository 
 
593
        if revtrees:
 
594
            basis_inv = revtrees[0].inventory
 
595
        else:
 
596
            basis_inv = self.repository.revision_tree(
 
597
                _mod_revision.NULL_REVISION).inventory
 
598
        if len(self.parents) > 0:
 
599
            if basis_revision_id != self.parents[0] and not ghost_basis:
 
600
                raise Exception(
 
601
                    "arbitrary basis parents not yet supported with merges")
 
602
            for revtree in revtrees[1:]:
 
603
                for change in revtree.inventory._make_delta(basis_inv):
 
604
                    if change[1] is None:
 
605
                        # Not present in this parent.
 
606
                        continue
 
607
                    if change[2] not in merged_ids:
 
608
                        if change[0] is not None:
 
609
                            basis_entry = basis_inv[change[2]]
 
610
                            merged_ids[change[2]] = [
 
611
                                # basis revid
 
612
                                basis_entry.revision,
 
613
                                # new tree revid
 
614
                                change[3].revision]
 
615
                            parent_entries[change[2]] = {
 
616
                                # basis parent
 
617
                                basis_entry.revision:basis_entry,
 
618
                                # this parent 
 
619
                                change[3].revision:change[3],
 
620
                                }
 
621
                        else:
 
622
                            merged_ids[change[2]] = [change[3].revision]
 
623
                            parent_entries[change[2]] = {change[3].revision:change[3]}
 
624
                    else:
 
625
                        merged_ids[change[2]].append(change[3].revision)
 
626
                        parent_entries[change[2]][change[3].revision] = change[3]
 
627
        else:
 
628
            merged_ids = {}
 
629
        # Setup the changes from the tree:
 
630
        # changes maps file_id -> (change, [parent revision_ids])
 
631
        changes= {}
 
632
        for change in iter_changes:
 
633
            # This probably looks up in basis_inv way to much.
 
634
            if change[1][0] is not None:
 
635
                head_candidate = [basis_inv[change[0]].revision]
 
636
            else:
 
637
                head_candidate = []
 
638
            changes[change[0]] = change, merged_ids.get(change[0],
 
639
                head_candidate)
 
640
        unchanged_merged = set(merged_ids) - set(changes)
 
641
        # Extend the changes dict with synthetic changes to record merges of
 
642
        # texts.
 
643
        for file_id in unchanged_merged:
 
644
            # Record a merged version of these items that did not change vs the
 
645
            # basis. This can be either identical parallel changes, or a revert
 
646
            # of a specific file after a merge. The recorded content will be
 
647
            # that of the current tree (which is the same as the basis), but
 
648
            # the per-file graph will reflect a merge.
 
649
            # NB:XXX: We are reconstructing path information we had, this
 
650
            # should be preserved instead.
 
651
            # inv delta  change: (file_id, (path_in_source, path_in_target),
 
652
            #   changed_content, versioned, parent, name, kind,
 
653
            #   executable)
 
654
            try:
 
655
                basis_entry = basis_inv[file_id]
 
656
            except errors.NoSuchId:
 
657
                # a change from basis->some_parents but file_id isn't in basis
 
658
                # so was new in the merge, which means it must have changed
 
659
                # from basis -> current, and as it hasn't the add was reverted
 
660
                # by the user. So we discard this change.
 
661
                pass
 
662
            else:
 
663
                change = (file_id,
 
664
                    (basis_inv.id2path(file_id), tree.id2path(file_id)),
 
665
                    False, (True, True),
 
666
                    (basis_entry.parent_id, basis_entry.parent_id),
 
667
                    (basis_entry.name, basis_entry.name),
 
668
                    (basis_entry.kind, basis_entry.kind),
 
669
                    (basis_entry.executable, basis_entry.executable))
 
670
                changes[file_id] = (change, merged_ids[file_id])
 
671
        # changes contains tuples with the change and a set of inventory
 
672
        # candidates for the file.
 
673
        # inv delta is:
 
674
        # old_path, new_path, file_id, new_inventory_entry
 
675
        seen_root = False # Is the root in the basis delta?
 
676
        inv_delta = self._basis_delta
 
677
        modified_rev = self._new_revision_id
 
678
        for change, head_candidates in changes.values():
 
679
            if change[3][1]: # versioned in target.
 
680
                # Several things may be happening here:
 
681
                # We may have a fork in the per-file graph
 
682
                #  - record a change with the content from tree
 
683
                # We may have a change against < all trees  
 
684
                #  - carry over the tree that hasn't changed
 
685
                # We may have a change against all trees
 
686
                #  - record the change with the content from tree
 
687
                kind = change[6][1]
 
688
                file_id = change[0]
 
689
                entry = _entry_factory[kind](file_id, change[5][1],
 
690
                    change[4][1])
 
691
                head_set = self._heads(change[0], set(head_candidates))
 
692
                heads = []
 
693
                # Preserve ordering.
 
694
                for head_candidate in head_candidates:
 
695
                    if head_candidate in head_set:
 
696
                        heads.append(head_candidate)
 
697
                        head_set.remove(head_candidate)
 
698
                carried_over = False
 
699
                if len(heads) == 1:
 
700
                    # Could be a carry-over situation:
 
701
                    parent_entry_revs = parent_entries.get(file_id, None)
 
702
                    if parent_entry_revs:
 
703
                        parent_entry = parent_entry_revs.get(heads[0], None)
 
704
                    else:
 
705
                        parent_entry = None
 
706
                    if parent_entry is None:
 
707
                        # The parent iter_changes was called against is the one
 
708
                        # that is the per-file head, so any change is relevant
 
709
                        # iter_changes is valid.
 
710
                        carry_over_possible = False
 
711
                    else:
 
712
                        # could be a carry over situation
 
713
                        # A change against the basis may just indicate a merge,
 
714
                        # we need to check the content against the source of the
 
715
                        # merge to determine if it was changed after the merge
 
716
                        # or carried over.
 
717
                        if (parent_entry.kind != entry.kind or
 
718
                            parent_entry.parent_id != entry.parent_id or
 
719
                            parent_entry.name != entry.name):
 
720
                            # Metadata common to all entries has changed
 
721
                            # against per-file parent
 
722
                            carry_over_possible = False
 
723
                        else:
 
724
                            carry_over_possible = True
 
725
                        # per-type checks for changes against the parent_entry
 
726
                        # are done below.
 
727
                else:
 
728
                    # Cannot be a carry-over situation
 
729
                    carry_over_possible = False
 
730
                # Populate the entry in the delta
 
731
                if kind == 'file':
 
732
                    # XXX: There is still a small race here: If someone reverts the content of a file
 
733
                    # after iter_changes examines and decides it has changed,
 
734
                    # we will unconditionally record a new version even if some
 
735
                    # other process reverts it while commit is running (with
 
736
                    # the revert happening after iter_changes did it's
 
737
                    # examination).
 
738
                    if change[7][1]:
 
739
                        entry.executable = True
 
740
                    else:
 
741
                        entry.executable = False
 
742
                    if (carry_over_possible and
 
743
                        parent_entry.executable == entry.executable):
 
744
                            # Check the file length, content hash after reading
 
745
                            # the file.
 
746
                            nostore_sha = parent_entry.text_sha1
 
747
                    else:
 
748
                        nostore_sha = None
 
749
                    file_obj, stat_value = tree.get_file_with_stat(file_id, change[1][1])
 
750
                    try:
 
751
                        text = file_obj.read()
 
752
                    finally:
 
753
                        file_obj.close()
 
754
                    try:
 
755
                        entry.text_sha1, entry.text_size = self._add_text_to_weave(
 
756
                            file_id, text, heads, nostore_sha)
 
757
                        yield file_id, change[1][1], (entry.text_sha1, stat_value)
 
758
                    except errors.ExistingContent:
 
759
                        # No content change against a carry_over parent
 
760
                        # Perhaps this should also yield a fs hash update?
 
761
                        carried_over = True
 
762
                        entry.text_size = parent_entry.text_size
 
763
                        entry.text_sha1 = parent_entry.text_sha1
 
764
                elif kind == 'symlink':
 
765
                    # Wants a path hint?
 
766
                    entry.symlink_target = tree.get_symlink_target(file_id)
 
767
                    if (carry_over_possible and
 
768
                        parent_entry.symlink_target == entry.symlink_target):
 
769
                        carried_over = True
 
770
                    else:
 
771
                        self._add_text_to_weave(change[0], '', heads, None)
 
772
                elif kind == 'directory':
 
773
                    if carry_over_possible:
 
774
                        carried_over = True
 
775
                    else:
 
776
                        # Nothing to set on the entry.
 
777
                        # XXX: split into the Root and nonRoot versions.
 
778
                        if change[1][1] != '' or self.repository.supports_rich_root():
 
779
                            self._add_text_to_weave(change[0], '', heads, None)
 
780
                elif kind == 'tree-reference':
 
781
                    if not self.repository._format.supports_tree_reference:
 
782
                        # This isn't quite sane as an error, but we shouldn't
 
783
                        # ever see this code path in practice: tree's don't
 
784
                        # permit references when the repo doesn't support tree
 
785
                        # references.
 
786
                        raise errors.UnsupportedOperation(tree.add_reference,
 
787
                            self.repository)
 
788
                    reference_revision = tree.get_reference_revision(change[0])
 
789
                    entry.reference_revision = reference_revision
 
790
                    if (carry_over_possible and
 
791
                        parent_entry.reference_revision == reference_revision):
 
792
                        carried_over = True
 
793
                    else:
 
794
                        self._add_text_to_weave(change[0], '', heads, None)
 
795
                else:
 
796
                    raise AssertionError('unknown kind %r' % kind)
 
797
                if not carried_over:
 
798
                    entry.revision = modified_rev
 
799
                else:
 
800
                    entry.revision = parent_entry.revision
 
801
            else:
 
802
                entry = None
 
803
            new_path = change[1][1]
 
804
            inv_delta.append((change[1][0], new_path, change[0], entry))
 
805
            if new_path == '':
 
806
                seen_root = True
 
807
        self.new_inventory = None
 
808
        if len(inv_delta):
 
809
            self._any_changes = True
 
810
        if not seen_root:
 
811
            # housekeeping root entry changes do not affect no-change commits.
 
812
            self._require_root_change(tree)
 
813
        self.basis_delta_revision = basis_revision_id
 
814
 
 
815
    def _add_text_to_weave(self, file_id, new_text, parents, nostore_sha):
 
816
        parent_keys = tuple([(file_id, parent) for parent in parents])
 
817
        return self.repository.texts._add_text(
 
818
            (file_id, self._new_revision_id), parent_keys, new_text,
 
819
            nostore_sha=nostore_sha, random_id=self.random_revid)[0:2]
513
820
 
514
821
 
515
822
class RootCommitBuilder(CommitBuilder):
527
834
        :param tree: The tree that is being committed.
528
835
        """
529
836
 
 
837
    def _require_root_change(self, tree):
 
838
        """Enforce an appropriate root object change.
 
839
 
 
840
        This is called once when record_iter_changes is called, if and only if
 
841
        the root was not in the delta calculated by record_iter_changes.
 
842
 
 
843
        :param tree: The tree which is being committed.
 
844
        """
 
845
        # versioned roots do not change unless the tree found a change.
 
846
 
530
847
 
531
848
######################################################################
532
849
# Repositories
533
850
 
 
851
 
534
852
class Repository(object):
535
853
    """Repository holding history for one or more branches.
536
854
 
539
857
    which views a particular line of development through that history.
540
858
 
541
859
    The Repository builds on top of some byte storage facilies (the revisions,
542
 
    signatures, inventories and texts attributes) and a Transport, which
543
 
    respectively provide byte storage and a means to access the (possibly
 
860
    signatures, inventories, texts and chk_bytes attributes) and a Transport,
 
861
    which respectively provide byte storage and a means to access the (possibly
544
862
    remote) disk.
545
863
 
546
864
    The byte storage facilities are addressed via tuples, which we refer to
547
865
    as 'keys' throughout the code base. Revision_keys, inventory_keys and
548
866
    signature_keys are all 1-tuples: (revision_id,). text_keys are two-tuples:
549
 
    (file_id, revision_id). We use this interface because it allows low
550
 
    friction with the underlying code that implements disk indices, network
551
 
    encoding and other parts of bzrlib.
 
867
    (file_id, revision_id). chk_bytes uses CHK keys - a 1-tuple with a single
 
868
    byte string made up of a hash identifier and a hash value.
 
869
    We use this interface because it allows low friction with the underlying
 
870
    code that implements disk indices, network encoding and other parts of
 
871
    bzrlib.
552
872
 
553
873
    :ivar revisions: A bzrlib.versionedfile.VersionedFiles instance containing
554
874
        the serialised revisions for the repository. This can be used to obtain
573
893
        The result of trying to insert data into the repository via this store
574
894
        is undefined: it should be considered read-only except for implementors
575
895
        of repositories.
 
896
    :ivar chk_bytes: A bzrlib.versionedfile.VersionedFiles instance containing
 
897
        any data the repository chooses to store or have indexed by its hash.
 
898
        The result of trying to insert data into the repository via this store
 
899
        is undefined: it should be considered read-only except for implementors
 
900
        of repositories.
576
901
    :ivar _transport: Transport for file access to repository, typically
577
902
        pointing to .bzr/repository.
578
903
    """
599
924
        """
600
925
        if self._write_group is not self.get_transaction():
601
926
            # has an unlock or relock occured ?
602
 
            raise errors.BzrError('mismatched lock context and write group.')
 
927
            raise errors.BzrError(
 
928
                'mismatched lock context and write group. %r, %r' %
 
929
                (self._write_group, self.get_transaction()))
603
930
        try:
604
931
            self._abort_write_group()
605
932
        except Exception, exc:
631
958
        """
632
959
        if not self._format.supports_external_lookups:
633
960
            raise errors.UnstackableRepositoryFormat(self._format, self.base)
 
961
        if self.is_locked():
 
962
            # This repository will call fallback.unlock() when we transition to
 
963
            # the unlocked state, so we make sure to increment the lock count
 
964
            repository.lock_read()
634
965
        self._check_fallback_repository(repository)
635
966
        self._fallback_repositories.append(repository)
636
967
        self.texts.add_fallback_versioned_files(repository.texts)
637
968
        self.inventories.add_fallback_versioned_files(repository.inventories)
638
969
        self.revisions.add_fallback_versioned_files(repository.revisions)
639
970
        self.signatures.add_fallback_versioned_files(repository.signatures)
 
971
        if self.chk_bytes is not None:
 
972
            self.chk_bytes.add_fallback_versioned_files(repository.chk_bytes)
640
973
 
641
974
    def _check_fallback_repository(self, repository):
642
975
        """Check that this repository can fallback to repository safely.
666
999
                % (inv.revision_id, revision_id))
667
1000
        if inv.root is None:
668
1001
            raise AssertionError()
 
1002
        return self._add_inventory_checked(revision_id, inv, parents)
 
1003
 
 
1004
    def _add_inventory_checked(self, revision_id, inv, parents):
 
1005
        """Add inv to the repository after checking the inputs.
 
1006
 
 
1007
        This function can be overridden to allow different inventory styles.
 
1008
 
 
1009
        :seealso: add_inventory, for the contract.
 
1010
        """
669
1011
        inv_lines = self._serialise_inventory_to_lines(inv)
670
1012
        return self._inventory_add_lines(revision_id, parents,
671
1013
            inv_lines, check_content=False)
672
1014
 
673
1015
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
674
 
                               parents):
 
1016
                               parents, basis_inv=None, propagate_caches=False):
675
1017
        """Add a new inventory expressed as a delta against another revision.
676
1018
 
 
1019
        See the inventory developers documentation for the theory behind
 
1020
        inventory deltas.
 
1021
 
677
1022
        :param basis_revision_id: The inventory id the delta was created
678
1023
            against. (This does not have to be a direct parent.)
679
1024
        :param delta: The inventory delta (see Inventory.apply_delta for
685
1030
            for repositories that depend on the inventory graph for revision
686
1031
            graph access, as well as for those that pun ancestry with delta
687
1032
            compression.
 
1033
        :param basis_inv: The basis inventory if it is already known,
 
1034
            otherwise None.
 
1035
        :param propagate_caches: If True, the caches for this inventory are
 
1036
          copied to and updated for the result if possible.
688
1037
 
689
1038
        :returns: (validator, new_inv)
690
1039
            The validator(which is a sha1 digest, though what is sha'd is
701
1050
            # inventory implementations may support: A better idiom would be to
702
1051
            # return a new inventory, but as there is no revision tree cache in
703
1052
            # repository this is safe for now - RBC 20081013
704
 
            basis_inv = basis_tree.inventory
 
1053
            if basis_inv is None:
 
1054
                basis_inv = basis_tree.inventory
705
1055
            basis_inv.apply_delta(delta)
706
1056
            basis_inv.revision_id = new_revision_id
707
1057
            return (self.add_inventory(new_revision_id, basis_inv, parents),
834
1184
        self._inventory_entry_cache = fifo_cache.FIFOCache(10*1024)
835
1185
 
836
1186
    def __repr__(self):
837
 
        return '%s(%r)' % (self.__class__.__name__,
838
 
                           self.base)
 
1187
        if self._fallback_repositories:
 
1188
            return '%s(%r, fallback_repositories=%r)' % (
 
1189
                self.__class__.__name__,
 
1190
                self.base,
 
1191
                self._fallback_repositories)
 
1192
        else:
 
1193
            return '%s(%r)' % (self.__class__.__name__,
 
1194
                               self.base)
 
1195
 
 
1196
    def _has_same_fallbacks(self, other_repo):
 
1197
        """Returns true if the repositories have the same fallbacks."""
 
1198
        my_fb = self._fallback_repositories
 
1199
        other_fb = other_repo._fallback_repositories
 
1200
        if len(my_fb) != len(other_fb):
 
1201
            return False
 
1202
        for f, g in zip(my_fb, other_fb):
 
1203
            if not f.has_same_location(g):
 
1204
                return False
 
1205
        return True
839
1206
 
840
1207
    def has_same_location(self, other):
841
1208
        """Returns a boolean indicating if this repository is at the same
884
1251
 
885
1252
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
886
1253
        """
 
1254
        locked = self.is_locked()
887
1255
        result = self.control_files.lock_write(token=token)
888
 
        for repo in self._fallback_repositories:
889
 
            # Writes don't affect fallback repos
890
 
            repo.lock_read()
891
 
        self._refresh_data()
 
1256
        if not locked:
 
1257
            for repo in self._fallback_repositories:
 
1258
                # Writes don't affect fallback repos
 
1259
                repo.lock_read()
 
1260
            self._refresh_data()
892
1261
        return result
893
1262
 
894
1263
    def lock_read(self):
 
1264
        locked = self.is_locked()
895
1265
        self.control_files.lock_read()
896
 
        for repo in self._fallback_repositories:
897
 
            repo.lock_read()
898
 
        self._refresh_data()
 
1266
        if not locked:
 
1267
            for repo in self._fallback_repositories:
 
1268
                repo.lock_read()
 
1269
            self._refresh_data()
899
1270
 
900
1271
    def get_physical_lock_status(self):
901
1272
        return self.control_files.get_physical_lock_status()
1023
1394
        return InterRepository.get(other, self).search_missing_revision_ids(
1024
1395
            revision_id, find_ghosts)
1025
1396
 
1026
 
    @deprecated_method(one_two)
1027
 
    @needs_read_lock
1028
 
    def missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1029
 
        """Return the revision ids that other has that this does not.
1030
 
 
1031
 
        These are returned in topological order.
1032
 
 
1033
 
        revision_id: only return revision ids included by revision_id.
1034
 
        """
1035
 
        keys =  self.search_missing_revision_ids(
1036
 
            other, revision_id, find_ghosts).get_keys()
1037
 
        other.lock_read()
1038
 
        try:
1039
 
            parents = other.get_graph().get_parent_map(keys)
1040
 
        finally:
1041
 
            other.unlock()
1042
 
        return tsort.topo_sort(parents)
1043
 
 
1044
1397
    @staticmethod
1045
1398
    def open(base):
1046
1399
        """Open the repository rooted at base.
1069
1422
            raise errors.BzrError('mismatched lock context %r and '
1070
1423
                'write group %r.' %
1071
1424
                (self.get_transaction(), self._write_group))
1072
 
        self._commit_write_group()
 
1425
        result = self._commit_write_group()
1073
1426
        self._write_group = None
 
1427
        return result
1074
1428
 
1075
1429
    def _commit_write_group(self):
1076
1430
        """Template method for per-repository write group cleanup.
1084
1438
    def suspend_write_group(self):
1085
1439
        raise errors.UnsuspendableWriteGroup(self)
1086
1440
 
 
1441
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
 
1442
        """Return the keys of missing inventory parents for revisions added in
 
1443
        this write group.
 
1444
 
 
1445
        A revision is not complete if the inventory delta for that revision
 
1446
        cannot be calculated.  Therefore if the parent inventories of a
 
1447
        revision are not present, the revision is incomplete, and e.g. cannot
 
1448
        be streamed by a smart server.  This method finds missing inventory
 
1449
        parents for revisions added in this write group.
 
1450
        """
 
1451
        if not self._format.supports_external_lookups:
 
1452
            # This is only an issue for stacked repositories
 
1453
            return set()
 
1454
        if not self.is_in_write_group():
 
1455
            raise AssertionError('not in a write group')
 
1456
 
 
1457
        # XXX: We assume that every added revision already has its
 
1458
        # corresponding inventory, so we only check for parent inventories that
 
1459
        # might be missing, rather than all inventories.
 
1460
        parents = set(self.revisions._index.get_missing_parents())
 
1461
        parents.discard(_mod_revision.NULL_REVISION)
 
1462
        unstacked_inventories = self.inventories._index
 
1463
        present_inventories = unstacked_inventories.get_parent_map(
 
1464
            key[-1:] for key in parents)
 
1465
        parents.difference_update(present_inventories)
 
1466
        if len(parents) == 0:
 
1467
            # No missing parent inventories.
 
1468
            return set()
 
1469
        if not check_for_missing_texts:
 
1470
            return set(('inventories', rev_id) for (rev_id,) in parents)
 
1471
        # Ok, now we have a list of missing inventories.  But these only matter
 
1472
        # if the inventories that reference them are missing some texts they
 
1473
        # appear to introduce.
 
1474
        # XXX: Texts referenced by all added inventories need to be present,
 
1475
        # but at the moment we're only checking for texts referenced by
 
1476
        # inventories at the graph's edge.
 
1477
        key_deps = self.revisions._index._key_dependencies
 
1478
        key_deps.add_keys(present_inventories)
 
1479
        referrers = frozenset(r[0] for r in key_deps.get_referrers())
 
1480
        file_ids = self.fileids_altered_by_revision_ids(referrers)
 
1481
        missing_texts = set()
 
1482
        for file_id, version_ids in file_ids.iteritems():
 
1483
            missing_texts.update(
 
1484
                (file_id, version_id) for version_id in version_ids)
 
1485
        present_texts = self.texts.get_parent_map(missing_texts)
 
1486
        missing_texts.difference_update(present_texts)
 
1487
        if not missing_texts:
 
1488
            # No texts are missing, so all revisions and their deltas are
 
1489
            # reconstructable.
 
1490
            return set()
 
1491
        # Alternatively the text versions could be returned as the missing
 
1492
        # keys, but this is likely to be less data.
 
1493
        missing_keys = set(('inventories', rev_id) for (rev_id,) in parents)
 
1494
        return missing_keys
 
1495
 
 
1496
    def refresh_data(self):
 
1497
        """Re-read any data needed to to synchronise with disk.
 
1498
 
 
1499
        This method is intended to be called after another repository instance
 
1500
        (such as one used by a smart server) has inserted data into the
 
1501
        repository. It may not be called during a write group, but may be
 
1502
        called at any other time.
 
1503
        """
 
1504
        if self.is_in_write_group():
 
1505
            raise errors.InternalBzrError(
 
1506
                "May not refresh_data while in a write group.")
 
1507
        self._refresh_data()
 
1508
 
1087
1509
    def resume_write_group(self, tokens):
1088
1510
        if not self.is_write_locked():
1089
1511
            raise errors.NotWriteLocked(self)
1096
1518
    def _resume_write_group(self, tokens):
1097
1519
        raise errors.UnsuspendableWriteGroup(self)
1098
1520
 
1099
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
 
1521
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
1522
            fetch_spec=None):
1100
1523
        """Fetch the content required to construct revision_id from source.
1101
1524
 
1102
 
        If revision_id is None all content is copied.
 
1525
        If revision_id is None and fetch_spec is None, then all content is
 
1526
        copied.
 
1527
 
 
1528
        fetch() may not be used when the repository is in a write group -
 
1529
        either finish the current write group before using fetch, or use
 
1530
        fetch before starting the write group.
 
1531
 
1103
1532
        :param find_ghosts: Find and copy revisions in the source that are
1104
1533
            ghosts in the target (and not reachable directly by walking out to
1105
1534
            the first-present revision in target from revision_id).
 
1535
        :param revision_id: If specified, all the content needed for this
 
1536
            revision ID will be copied to the target.  Fetch will determine for
 
1537
            itself which content needs to be copied.
 
1538
        :param fetch_spec: If specified, a SearchResult or
 
1539
            PendingAncestryResult that describes which revisions to copy.  This
 
1540
            allows copying multiple heads at once.  Mutually exclusive with
 
1541
            revision_id.
1106
1542
        """
 
1543
        if fetch_spec is not None and revision_id is not None:
 
1544
            raise AssertionError(
 
1545
                "fetch_spec and revision_id are mutually exclusive.")
 
1546
        if self.is_in_write_group():
 
1547
            raise errors.InternalBzrError(
 
1548
                "May not fetch while in a write group.")
1107
1549
        # fast path same-url fetch operations
1108
 
        if self.has_same_location(source):
 
1550
        # TODO: lift out to somewhere common with RemoteRepository
 
1551
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
 
1552
        if (self.has_same_location(source)
 
1553
            and fetch_spec is None
 
1554
            and self._has_same_fallbacks(source)):
1109
1555
            # check that last_revision is in 'from' and then return a
1110
1556
            # no-operation.
1111
1557
            if (revision_id is not None and
1117
1563
        # IncompatibleRepositories when asked to fetch.
1118
1564
        inter = InterRepository.get(source, self)
1119
1565
        return inter.fetch(revision_id=revision_id, pb=pb,
1120
 
            find_ghosts=find_ghosts)
 
1566
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1121
1567
 
1122
1568
    def create_bundle(self, target, base, fileobj, format=None):
1123
1569
        return serializer.write_bundle(self, target, base, fileobj, format)
1152
1598
        self.control_files.unlock()
1153
1599
        if self.control_files._lock_count == 0:
1154
1600
            self._inventory_entry_cache.clear()
1155
 
        for repo in self._fallback_repositories:
1156
 
            repo.unlock()
 
1601
            for repo in self._fallback_repositories:
 
1602
                repo.unlock()
1157
1603
 
1158
1604
    @needs_read_lock
1159
1605
    def clone(self, a_bzrdir, revision_id=None):
1293
1739
        # TODO: jam 20070210 This shouldn't be necessary since get_revision
1294
1740
        #       would have already do it.
1295
1741
        # TODO: jam 20070210 Just use _serializer.write_revision_to_string()
 
1742
        # TODO: this can't just be replaced by:
 
1743
        # return self._serializer.write_revision_to_string(
 
1744
        #     self.get_revision(revision_id))
 
1745
        # as cStringIO preservers the encoding unlike write_revision_to_string
 
1746
        # or some other call down the path.
1296
1747
        rev = self.get_revision(revision_id)
1297
1748
        rev_tmp = cStringIO.StringIO()
1298
1749
        # the current serializer..
1300
1751
        rev_tmp.seek(0)
1301
1752
        return rev_tmp.getvalue()
1302
1753
 
1303
 
    def get_deltas_for_revisions(self, revisions):
 
1754
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1304
1755
        """Produce a generator of revision deltas.
1305
1756
 
1306
1757
        Note that the input is a sequence of REVISIONS, not revision_ids.
1307
1758
        Trees will be held in memory until the generator exits.
1308
1759
        Each delta is relative to the revision's lefthand predecessor.
 
1760
 
 
1761
        :param specific_fileids: if not None, the result is filtered
 
1762
          so that only those file-ids, their parents and their
 
1763
          children are included.
1309
1764
        """
 
1765
        # Get the revision-ids of interest
1310
1766
        required_trees = set()
1311
1767
        for revision in revisions:
1312
1768
            required_trees.add(revision.revision_id)
1313
1769
            required_trees.update(revision.parent_ids[:1])
1314
 
        trees = dict((t.get_revision_id(), t) for
1315
 
                     t in self.revision_trees(required_trees))
 
1770
 
 
1771
        # Get the matching filtered trees. Note that it's more
 
1772
        # efficient to pass filtered trees to changes_from() rather
 
1773
        # than doing the filtering afterwards. changes_from() could
 
1774
        # arguably do the filtering itself but it's path-based, not
 
1775
        # file-id based, so filtering before or afterwards is
 
1776
        # currently easier.
 
1777
        if specific_fileids is None:
 
1778
            trees = dict((t.get_revision_id(), t) for
 
1779
                t in self.revision_trees(required_trees))
 
1780
        else:
 
1781
            trees = dict((t.get_revision_id(), t) for
 
1782
                t in self._filtered_revision_trees(required_trees,
 
1783
                specific_fileids))
 
1784
 
 
1785
        # Calculate the deltas
1316
1786
        for revision in revisions:
1317
1787
            if not revision.parent_ids:
1318
1788
                old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
1321
1791
            yield trees[revision.revision_id].changes_from(old_tree)
1322
1792
 
1323
1793
    @needs_read_lock
1324
 
    def get_revision_delta(self, revision_id):
 
1794
    def get_revision_delta(self, revision_id, specific_fileids=None):
1325
1795
        """Return the delta for one revision.
1326
1796
 
1327
1797
        The delta is relative to the left-hand predecessor of the
1328
1798
        revision.
 
1799
 
 
1800
        :param specific_fileids: if not None, the result is filtered
 
1801
          so that only those file-ids, their parents and their
 
1802
          children are included.
1329
1803
        """
1330
1804
        r = self.get_revision(revision_id)
1331
 
        return list(self.get_deltas_for_revisions([r]))[0]
 
1805
        return list(self.get_deltas_for_revisions([r],
 
1806
            specific_fileids=specific_fileids))[0]
1332
1807
 
1333
1808
    @needs_write_lock
1334
1809
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1343
1818
    def find_text_key_references(self):
1344
1819
        """Find the text key references within the repository.
1345
1820
 
1346
 
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
1347
 
        revision_ids. Each altered file-ids has the exact revision_ids that
1348
 
        altered it listed explicitly.
1349
1821
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1350
1822
            to whether they were referred to by the inventory of the
1351
1823
            revision_id that they contain. The inventory texts from all present
1440
1912
                result[key] = True
1441
1913
        return result
1442
1914
 
 
1915
    def _inventory_xml_lines_for_keys(self, keys):
 
1916
        """Get a line iterator of the sort needed for findind references.
 
1917
 
 
1918
        Not relevant for non-xml inventory repositories.
 
1919
 
 
1920
        Ghosts in revision_keys are ignored.
 
1921
 
 
1922
        :param revision_keys: The revision keys for the inventories to inspect.
 
1923
        :return: An iterator over (inventory line, revid) for the fulltexts of
 
1924
            all of the xml inventories specified by revision_keys.
 
1925
        """
 
1926
        stream = self.inventories.get_record_stream(keys, 'unordered', True)
 
1927
        for record in stream:
 
1928
            if record.storage_kind != 'absent':
 
1929
                chunks = record.get_bytes_as('chunked')
 
1930
                revid = record.key[-1]
 
1931
                lines = osutils.chunks_to_lines(chunks)
 
1932
                for line in lines:
 
1933
                    yield line, revid
 
1934
 
1443
1935
    def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
1444
 
        revision_ids):
 
1936
        revision_keys):
1445
1937
        """Helper routine for fileids_altered_by_revision_ids.
1446
1938
 
1447
1939
        This performs the translation of xml lines to revision ids.
1448
1940
 
1449
1941
        :param line_iterator: An iterator of lines, origin_version_id
1450
 
        :param revision_ids: The revision ids to filter for. This should be a
 
1942
        :param revision_keys: The revision ids to filter for. This should be a
1451
1943
            set or other type which supports efficient __contains__ lookups, as
1452
 
            the revision id from each parsed line will be looked up in the
1453
 
            revision_ids filter.
 
1944
            the revision key from each parsed line will be looked up in the
 
1945
            revision_keys filter.
1454
1946
        :return: a dictionary mapping altered file-ids to an iterable of
1455
1947
        revision_ids. Each altered file-ids has the exact revision_ids that
1456
1948
        altered it listed explicitly.
1457
1949
        """
 
1950
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
1951
                line_iterator).iterkeys())
 
1952
        parent_keys = self._find_parent_keys_of_revisions(revision_keys)
 
1953
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
1954
            self._inventory_xml_lines_for_keys(parent_keys)))
 
1955
        new_keys = seen - parent_seen
1458
1956
        result = {}
1459
1957
        setdefault = result.setdefault
1460
 
        for key in \
1461
 
            self._find_text_key_references_from_xml_inventory_lines(
1462
 
                line_iterator).iterkeys():
1463
 
            # once data is all ensured-consistent; then this is
1464
 
            # if revision_id == version_id
1465
 
            if key[-1:] in revision_ids:
1466
 
                setdefault(key[0], set()).add(key[-1])
 
1958
        for key in new_keys:
 
1959
            setdefault(key[0], set()).add(key[-1])
1467
1960
        return result
1468
1961
 
 
1962
    def _find_parent_ids_of_revisions(self, revision_ids):
 
1963
        """Find all parent ids that are mentioned in the revision graph.
 
1964
 
 
1965
        :return: set of revisions that are parents of revision_ids which are
 
1966
            not part of revision_ids themselves
 
1967
        """
 
1968
        parent_map = self.get_parent_map(revision_ids)
 
1969
        parent_ids = set()
 
1970
        map(parent_ids.update, parent_map.itervalues())
 
1971
        parent_ids.difference_update(revision_ids)
 
1972
        parent_ids.discard(_mod_revision.NULL_REVISION)
 
1973
        return parent_ids
 
1974
 
 
1975
    def _find_parent_keys_of_revisions(self, revision_keys):
 
1976
        """Similar to _find_parent_ids_of_revisions, but used with keys.
 
1977
 
 
1978
        :param revision_keys: An iterable of revision_keys.
 
1979
        :return: The parents of all revision_keys that are not already in
 
1980
            revision_keys
 
1981
        """
 
1982
        parent_map = self.revisions.get_parent_map(revision_keys)
 
1983
        parent_keys = set()
 
1984
        map(parent_keys.update, parent_map.itervalues())
 
1985
        parent_keys.difference_update(revision_keys)
 
1986
        parent_keys.discard(_mod_revision.NULL_REVISION)
 
1987
        return parent_keys
 
1988
 
1469
1989
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
1470
1990
        """Find the file ids and versions affected by revisions.
1471
1991
 
1512
2032
        for record in self.texts.get_record_stream(text_keys, 'unordered', True):
1513
2033
            if record.storage_kind == 'absent':
1514
2034
                raise errors.RevisionNotPresent(record.key, self)
1515
 
            yield text_keys[record.key], record.get_bytes_as('fulltext')
 
2035
            yield text_keys[record.key], record.get_bytes_as('chunked')
1516
2036
 
1517
2037
    def _generate_text_key_index(self, text_key_references=None,
1518
2038
        ancestors=None):
1567
2087
        batch_size = 10 # should be ~150MB on a 55K path tree
1568
2088
        batch_count = len(revision_order) / batch_size + 1
1569
2089
        processed_texts = 0
1570
 
        pb.update("Calculating text parents.", processed_texts, text_count)
 
2090
        pb.update("Calculating text parents", processed_texts, text_count)
1571
2091
        for offset in xrange(batch_count):
1572
2092
            to_query = revision_order[offset * batch_size:(offset + 1) *
1573
2093
                batch_size]
1577
2097
                revision_id = rev_tree.get_revision_id()
1578
2098
                parent_ids = ancestors[revision_id]
1579
2099
                for text_key in revision_keys[revision_id]:
1580
 
                    pb.update("Calculating text parents.", processed_texts)
 
2100
                    pb.update("Calculating text parents", processed_texts)
1581
2101
                    processed_texts += 1
1582
2102
                    candidate_parents = []
1583
2103
                    for parent_id in parent_ids:
1599
2119
                            except KeyError:
1600
2120
                                inv = self.revision_tree(parent_id).inventory
1601
2121
                                inventory_cache[parent_id] = inv
1602
 
                            parent_entry = inv._byid.get(text_key[0], None)
 
2122
                            try:
 
2123
                                parent_entry = inv[text_key[0]]
 
2124
                            except (KeyError, errors.NoSuchId):
 
2125
                                parent_entry = None
1603
2126
                            if parent_entry is not None:
1604
2127
                                parent_text_key = (
1605
2128
                                    text_key[0], parent_entry.revision)
1630
2153
            versions).  knit-kind is one of 'file', 'inventory', 'signatures',
1631
2154
            'revisions'.  file-id is None unless knit-kind is 'file'.
1632
2155
        """
 
2156
        for result in self._find_file_keys_to_fetch(revision_ids, _files_pb):
 
2157
            yield result
 
2158
        del _files_pb
 
2159
        for result in self._find_non_file_keys_to_fetch(revision_ids):
 
2160
            yield result
 
2161
 
 
2162
    def _find_file_keys_to_fetch(self, revision_ids, pb):
1633
2163
        # XXX: it's a bit weird to control the inventory weave caching in this
1634
2164
        # generator.  Ideally the caching would be done in fetch.py I think.  Or
1635
2165
        # maybe this generator should explicitly have the contract that it
1642
2172
        count = 0
1643
2173
        num_file_ids = len(file_ids)
1644
2174
        for file_id, altered_versions in file_ids.iteritems():
1645
 
            if _files_pb is not None:
1646
 
                _files_pb.update("fetch texts", count, num_file_ids)
 
2175
            if pb is not None:
 
2176
                pb.update("fetch texts", count, num_file_ids)
1647
2177
            count += 1
1648
2178
            yield ("file", file_id, altered_versions)
1649
 
        # We're done with the files_pb.  Note that it finished by the caller,
1650
 
        # just as it was created by the caller.
1651
 
        del _files_pb
1652
2179
 
 
2180
    def _find_non_file_keys_to_fetch(self, revision_ids):
1653
2181
        # inventory
1654
2182
        yield ("inventory", None, revision_ids)
1655
2183
 
1679
2207
        inventories in memory, but will only parse a single inventory at a
1680
2208
        time.
1681
2209
 
 
2210
        :param revision_ids: The expected revision ids of the inventories.
1682
2211
        :return: An iterator of inventories.
1683
2212
        """
1684
2213
        if ((None in revision_ids)
1742
2271
        """
1743
2272
        return self.get_revision(revision_id).inventory_sha1
1744
2273
 
 
2274
    def get_rev_id_for_revno(self, revno, known_pair):
 
2275
        """Return the revision id of a revno, given a later (revno, revid)
 
2276
        pair in the same history.
 
2277
 
 
2278
        :return: if found (True, revid).  If the available history ran out
 
2279
            before reaching the revno, then this returns
 
2280
            (False, (closest_revno, closest_revid)).
 
2281
        """
 
2282
        known_revno, known_revid = known_pair
 
2283
        partial_history = [known_revid]
 
2284
        distance_from_known = known_revno - revno
 
2285
        if distance_from_known < 0:
 
2286
            raise ValueError(
 
2287
                'requested revno (%d) is later than given known revno (%d)'
 
2288
                % (revno, known_revno))
 
2289
        try:
 
2290
            _iter_for_revno(
 
2291
                self, partial_history, stop_index=distance_from_known)
 
2292
        except errors.RevisionNotPresent, err:
 
2293
            if err.revision_id == known_revid:
 
2294
                # The start revision (known_revid) wasn't found.
 
2295
                raise
 
2296
            # This is a stacked repository with no fallbacks, or a there's a
 
2297
            # left-hand ghost.  Either way, even though the revision named in
 
2298
            # the error isn't in this repo, we know it's the next step in this
 
2299
            # left-hand history.
 
2300
            partial_history.append(err.revision_id)
 
2301
        if len(partial_history) <= distance_from_known:
 
2302
            # Didn't find enough history to get a revid for the revno.
 
2303
            earliest_revno = known_revno - len(partial_history) + 1
 
2304
            return (False, (earliest_revno, partial_history[-1]))
 
2305
        if len(partial_history) - 1 > distance_from_known:
 
2306
            raise AssertionError('_iter_for_revno returned too much history')
 
2307
        return (True, partial_history[-1])
 
2308
 
1745
2309
    def iter_reverse_revision_history(self, revision_id):
1746
2310
        """Iterate backwards through revision ids in the lefthand history
1747
2311
 
1753
2317
        while True:
1754
2318
            if next_id in (None, _mod_revision.NULL_REVISION):
1755
2319
                return
 
2320
            try:
 
2321
                parents = graph.get_parent_map([next_id])[next_id]
 
2322
            except KeyError:
 
2323
                raise errors.RevisionNotPresent(next_id, self)
1756
2324
            yield next_id
1757
 
            # Note: The following line may raise KeyError in the event of
1758
 
            # truncated history. We decided not to have a try:except:raise
1759
 
            # RevisionNotPresent here until we see a use for it, because of the
1760
 
            # cost in an inner loop that is by its very nature O(history).
1761
 
            # Robert Collins 20080326
1762
 
            parents = graph.get_parent_map([next_id])[next_id]
1763
2325
            if len(parents) == 0:
1764
2326
                return
1765
2327
            else:
1800
2362
        for repositories to maintain loaded indices across multiple locks
1801
2363
        by checking inside their implementation of this method to see
1802
2364
        whether their indices are still valid. This depends of course on
1803
 
        the disk format being validatable in this manner.
 
2365
        the disk format being validatable in this manner. This method is
 
2366
        also called by the refresh_data() public interface to cause a refresh
 
2367
        to occur while in a write lock so that data inserted by a smart server
 
2368
        push operation is visible on the client's instance of the physical
 
2369
        repository.
1804
2370
        """
1805
2371
 
1806
2372
    @needs_read_lock
1820
2386
            return RevisionTree(self, inv, revision_id)
1821
2387
 
1822
2388
    def revision_trees(self, revision_ids):
1823
 
        """Return Tree for a revision on this branch.
 
2389
        """Return Trees for revisions in this repository.
1824
2390
 
1825
 
        `revision_id` may not be None or 'null:'"""
 
2391
        :param revision_ids: a sequence of revision-ids;
 
2392
          a revision-id may not be None or 'null:'
 
2393
        """
1826
2394
        inventories = self.iter_inventories(revision_ids)
1827
2395
        for inv in inventories:
1828
2396
            yield RevisionTree(self, inv, inv.revision_id)
1829
2397
 
 
2398
    def _filtered_revision_trees(self, revision_ids, file_ids):
 
2399
        """Return Tree for a revision on this branch with only some files.
 
2400
 
 
2401
        :param revision_ids: a sequence of revision-ids;
 
2402
          a revision-id may not be None or 'null:'
 
2403
        :param file_ids: if not None, the result is filtered
 
2404
          so that only those file-ids, their parents and their
 
2405
          children are included.
 
2406
        """
 
2407
        inventories = self.iter_inventories(revision_ids)
 
2408
        for inv in inventories:
 
2409
            # Should we introduce a FilteredRevisionTree class rather
 
2410
            # than pre-filter the inventory here?
 
2411
            filtered_inv = inv.filter(file_ids)
 
2412
            yield RevisionTree(self, filtered_inv, filtered_inv.revision_id)
 
2413
 
1830
2414
    @needs_read_lock
1831
2415
    def get_ancestry(self, revision_id, topo_sorted=True):
1832
2416
        """Return a list of revision-ids integrated by a revision.
1857
2441
            keys = tsort.topo_sort(parent_map)
1858
2442
        return [None] + list(keys)
1859
2443
 
1860
 
    def pack(self):
 
2444
    def pack(self, hint=None):
1861
2445
        """Compress the data within the repository.
1862
2446
 
1863
2447
        This operation only makes sense for some repository types. For other
1866
2450
        This stub method does not require a lock, but subclasses should use
1867
2451
        @needs_write_lock as this is a long running call its reasonable to
1868
2452
        implicitly lock for the user.
1869
 
        """
1870
 
 
1871
 
    @needs_read_lock
1872
 
    @deprecated_method(one_six)
1873
 
    def print_file(self, file, revision_id):
1874
 
        """Print `file` to stdout.
1875
 
 
1876
 
        FIXME RBC 20060125 as John Meinel points out this is a bad api
1877
 
        - it writes to stdout, it assumes that that is valid etc. Fix
1878
 
        by creating a new more flexible convenience function.
1879
 
        """
1880
 
        tree = self.revision_tree(revision_id)
1881
 
        # use inventory as it was in that revision
1882
 
        file_id = tree.inventory.path2id(file)
1883
 
        if not file_id:
1884
 
            # TODO: jam 20060427 Write a test for this code path
1885
 
            #       it had a bug in it, and was raising the wrong
1886
 
            #       exception.
1887
 
            raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
1888
 
        tree.print_file(file_id)
 
2453
 
 
2454
        :param hint: If not supplied, the whole repository is packed.
 
2455
            If supplied, the repository may use the hint parameter as a
 
2456
            hint for the parts of the repository to pack. A hint can be
 
2457
            obtained from the result of commit_write_group(). Out of
 
2458
            date hints are simply ignored, because concurrent operations
 
2459
            can obsolete them rapidly.
 
2460
        """
1889
2461
 
1890
2462
    def get_transaction(self):
1891
2463
        return self.control_files.get_transaction()
1892
2464
 
1893
 
    @deprecated_method(one_one)
1894
 
    def get_parents(self, revision_ids):
1895
 
        """See StackedParentsProvider.get_parents"""
1896
 
        parent_map = self.get_parent_map(revision_ids)
1897
 
        return [parent_map.get(r, None) for r in revision_ids]
1898
 
 
1899
2465
    def get_parent_map(self, revision_ids):
1900
 
        """See graph._StackedParentsProvider.get_parent_map"""
 
2466
        """See graph.StackedParentsProvider.get_parent_map"""
1901
2467
        # revisions index works in keys; this just works in revisions
1902
2468
        # therefore wrap and unwrap
1903
2469
        query_keys = []
1926
2492
        parents_provider = self._make_parents_provider()
1927
2493
        if (other_repository is not None and
1928
2494
            not self.has_same_location(other_repository)):
1929
 
            parents_provider = graph._StackedParentsProvider(
 
2495
            parents_provider = graph.StackedParentsProvider(
1930
2496
                [parents_provider, other_repository._make_parents_provider()])
1931
2497
        return graph.Graph(parents_provider)
1932
2498
 
1933
 
    def _get_versioned_file_checker(self):
1934
 
        """Return an object suitable for checking versioned files."""
1935
 
        return _VersionedFileChecker(self)
 
2499
    def _get_versioned_file_checker(self, text_key_references=None):
 
2500
        """Return an object suitable for checking versioned files.
 
2501
        
 
2502
        :param text_key_references: if non-None, an already built
 
2503
            dictionary mapping text keys ((fileid, revision_id) tuples)
 
2504
            to whether they were referred to by the inventory of the
 
2505
            revision_id that they contain. If None, this will be
 
2506
            calculated.
 
2507
        """
 
2508
        return _VersionedFileChecker(self,
 
2509
            text_key_references=text_key_references)
1936
2510
 
1937
2511
    def revision_ids_to_search_result(self, result_set):
1938
2512
        """Convert a set of revision ids to a graph SearchResult."""
2089
2663
    """
2090
2664
    repository.start_write_group()
2091
2665
    try:
 
2666
        inventory_cache = lru_cache.LRUCache(10)
2092
2667
        for n, (revision, revision_tree, signature) in enumerate(iterable):
2093
 
            _install_revision(repository, revision, revision_tree, signature)
 
2668
            _install_revision(repository, revision, revision_tree, signature,
 
2669
                inventory_cache)
2094
2670
            if pb is not None:
2095
2671
                pb.update('Transferring revisions', n + 1, num_revisions)
2096
2672
    except:
2100
2676
        repository.commit_write_group()
2101
2677
 
2102
2678
 
2103
 
def _install_revision(repository, rev, revision_tree, signature):
 
2679
def _install_revision(repository, rev, revision_tree, signature,
 
2680
    inventory_cache):
2104
2681
    """Install all revision data into a repository."""
2105
2682
    present_parents = []
2106
2683
    parent_trees = {}
2143
2720
        repository.texts.add_lines(text_key, text_parents, lines)
2144
2721
    try:
2145
2722
        # install the inventory
2146
 
        repository.add_inventory(rev.revision_id, inv, present_parents)
 
2723
        if repository._format._commit_inv_deltas and len(rev.parent_ids):
 
2724
            # Cache this inventory
 
2725
            inventory_cache[rev.revision_id] = inv
 
2726
            try:
 
2727
                basis_inv = inventory_cache[rev.parent_ids[0]]
 
2728
            except KeyError:
 
2729
                repository.add_inventory(rev.revision_id, inv, present_parents)
 
2730
            else:
 
2731
                delta = inv._make_delta(basis_inv)
 
2732
                repository.add_inventory_by_delta(rev.parent_ids[0], delta,
 
2733
                    rev.revision_id, present_parents)
 
2734
        else:
 
2735
            repository.add_inventory(rev.revision_id, inv, present_parents)
2147
2736
    except errors.RevisionAlreadyPresent:
2148
2737
        pass
2149
2738
    if signature is not None:
2240
2829
 
2241
2830
    Once a format is deprecated, just deprecate the initialize and open
2242
2831
    methods on the format class. Do not deprecate the object, as the
2243
 
    object may be created even when a repository instnace hasn't been
 
2832
    object may be created even when a repository instance hasn't been
2244
2833
    created.
2245
2834
 
2246
2835
    Common instance attributes:
2256
2845
    # Can this repository be given external locations to lookup additional
2257
2846
    # data. Set to True or False in derived classes.
2258
2847
    supports_external_lookups = None
 
2848
    # Does this format support CHK bytestring lookups. Set to True or False in
 
2849
    # derived classes.
 
2850
    supports_chks = None
 
2851
    # Should commit add an inventory, or an inventory delta to the repository.
 
2852
    _commit_inv_deltas = True
2259
2853
    # What order should fetch operations request streams in?
2260
2854
    # The default is unordered as that is the cheapest for an origin to
2261
2855
    # provide.
2268
2862
    # Should fetch trigger a reconcile after the fetch? Only needed for
2269
2863
    # some repository formats that can suffer internal inconsistencies.
2270
2864
    _fetch_reconcile = False
 
2865
    # Does this format have < O(tree_size) delta generation. Used to hint what
 
2866
    # code path for commit, amongst other things.
 
2867
    fast_deltas = None
 
2868
    # Does doing a pack operation compress data? Useful for the pack UI command
 
2869
    # (so if there is one pack, the operation can still proceed because it may
 
2870
    # help), and for fetching when data won't have come from the same
 
2871
    # compressor.
 
2872
    pack_compresses = False
2271
2873
 
2272
2874
    def __str__(self):
2273
2875
        return "<%s>" % self.__class__.__name__
2439
3041
# Pre-0.8 formats that don't have a disk format string (because they are
2440
3042
# versioned by the matching control directory). We use the control directories
2441
3043
# disk format string as a key for the network_name because they meet the
2442
 
# constraints (simple string, unique, immmutable).
 
3044
# constraints (simple string, unique, immutable).
2443
3045
network_format_registry.register_lazy(
2444
3046
    "Bazaar-NG branch, format 5\n",
2445
3047
    'bzrlib.repofmt.weaverepo',
2526
3128
    )
2527
3129
 
2528
3130
# Development formats.
2529
 
# 1.7->1.8 go below here
2530
 
format_registry.register_lazy(
2531
 
    "Bazaar development format 2 (needs bzr.dev from before 1.8)\n",
2532
 
    'bzrlib.repofmt.pack_repo',
2533
 
    'RepositoryFormatPackDevelopment2',
2534
 
    )
 
3131
# Obsolete but kept pending a CHK based subtree format.
2535
3132
format_registry.register_lazy(
2536
3133
    ("Bazaar development format 2 with subtree support "
2537
3134
        "(needs bzr.dev from before 1.8)\n"),
2539
3136
    'RepositoryFormatPackDevelopment2Subtree',
2540
3137
    )
2541
3138
 
 
3139
# 1.14->1.16 go below here
 
3140
format_registry.register_lazy(
 
3141
    'Bazaar development format - group compression and chk inventory'
 
3142
        ' (needs bzr.dev from 1.14)\n',
 
3143
    'bzrlib.repofmt.groupcompress_repo',
 
3144
    'RepositoryFormatCHK1',
 
3145
    )
 
3146
 
 
3147
format_registry.register_lazy(
 
3148
    'Bazaar development format - chk repository with bencode revision '
 
3149
        'serialization (needs bzr.dev from 1.16)\n',
 
3150
    'bzrlib.repofmt.groupcompress_repo',
 
3151
    'RepositoryFormatCHK2',
 
3152
    )
 
3153
format_registry.register_lazy(
 
3154
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
3155
    'bzrlib.repofmt.groupcompress_repo',
 
3156
    'RepositoryFormat2a',
 
3157
    )
 
3158
 
2542
3159
 
2543
3160
class InterRepository(InterObject):
2544
3161
    """This class represents operations taking place between two repositories.
2552
3169
    InterRepository.get(other).method_name(parameters).
2553
3170
    """
2554
3171
 
2555
 
    _walk_to_common_revisions_batch_size = 1
 
3172
    _walk_to_common_revisions_batch_size = 50
2556
3173
    _optimisers = []
2557
3174
    """The available optimised InterRepository types."""
2558
3175
 
2559
 
    def __init__(self, source, target):
2560
 
        InterObject.__init__(self, source, target)
2561
 
        # These two attributes may be overridden by e.g. InterOtherToRemote to
2562
 
        # provide a faster implementation.
2563
 
        self.target_get_graph = self.target.get_graph
2564
 
        self.target_get_parent_map = self.target.get_parent_map
2565
 
 
2566
3176
    @needs_write_lock
2567
3177
    def copy_content(self, revision_id=None):
2568
3178
        """Make a complete copy of the content in self into destination.
2579
3189
            pass
2580
3190
        self.target.fetch(self.source, revision_id=revision_id)
2581
3191
 
2582
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
3192
    @needs_write_lock
 
3193
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3194
            fetch_spec=None):
2583
3195
        """Fetch the content required to construct revision_id.
2584
3196
 
2585
3197
        The content is copied from self.source to self.target.
2591
3203
        :return: None.
2592
3204
        """
2593
3205
        from bzrlib.fetch import RepoFetcher
2594
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2595
 
               self.source, self.source._format, self.target,
2596
 
               self.target._format)
2597
3206
        f = RepoFetcher(to_repository=self.target,
2598
3207
                               from_repository=self.source,
2599
3208
                               last_revision=revision_id,
 
3209
                               fetch_spec=fetch_spec,
2600
3210
                               pb=pb, find_ghosts=find_ghosts)
2601
3211
 
2602
3212
    def _walk_to_common_revisions(self, revision_ids):
2605
3215
        :param revision_ids: The start point for the search.
2606
3216
        :return: A set of revision ids.
2607
3217
        """
2608
 
        target_graph = self.target_get_graph()
 
3218
        target_graph = self.target.get_graph()
2609
3219
        revision_ids = frozenset(revision_ids)
2610
 
        # Fast path for the case where all the revisions are already in the
2611
 
        # target repo.
2612
 
        # (Although this does incur an extra round trip for the
2613
 
        # fairly common case where the target doesn't already have the revision
2614
 
        # we're pushing.)
2615
 
        if set(target_graph.get_parent_map(revision_ids)) == revision_ids:
2616
 
            return graph.SearchResult(revision_ids, set(), 0, set())
2617
3220
        missing_revs = set()
2618
3221
        source_graph = self.source.get_graph()
2619
3222
        # ensure we don't pay silly lookup costs.
2658
3261
                break
2659
3262
        return searcher.get_result()
2660
3263
 
2661
 
    @deprecated_method(one_two)
2662
 
    @needs_read_lock
2663
 
    def missing_revision_ids(self, revision_id=None, find_ghosts=True):
2664
 
        """Return the revision ids that source has that target does not.
2665
 
 
2666
 
        These are returned in topological order.
2667
 
 
2668
 
        :param revision_id: only return revision ids included by this
2669
 
                            revision_id.
2670
 
        :param find_ghosts: If True find missing revisions in deep history
2671
 
            rather than just finding the surface difference.
2672
 
        """
2673
 
        return list(self.search_missing_revision_ids(
2674
 
            revision_id, find_ghosts).get_keys())
2675
 
 
2676
3264
    @needs_read_lock
2677
3265
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2678
3266
        """Return the revision ids that source has that target does not.
2811
3399
        else:
2812
3400
            self.target.fetch(self.source, revision_id=revision_id)
2813
3401
 
2814
 
    @needs_write_lock
2815
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2816
 
        """See InterRepository.fetch()."""
2817
 
        from bzrlib.fetch import RepoFetcher
2818
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2819
 
               self.source, self.source._format, self.target, self.target._format)
2820
 
        f = RepoFetcher(to_repository=self.target,
2821
 
                               from_repository=self.source,
2822
 
                               last_revision=revision_id,
2823
 
                               pb=pb, find_ghosts=find_ghosts)
2824
 
 
2825
3402
    @needs_read_lock
2826
3403
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2827
3404
        """See InterRepository.missing_revision_ids()."""
2832
3409
        # so the first thing is to get a subset of the revisions to
2833
3410
        # satisfy revision_id in source, and then eliminate those that
2834
3411
        # we do already have.
2835
 
        # this is slow on high latency connection to self, but as as this
 
3412
        # this is slow on high latency connection to self, but as this
2836
3413
        # disk format scales terribly for push anyway due to rewriting
2837
3414
        # inventory.weave, this is considered acceptable.
2838
3415
        # - RBC 20060209
2891
3468
            return False
2892
3469
        return are_knits and InterRepository._same_model(source, target)
2893
3470
 
2894
 
    @needs_write_lock
2895
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2896
 
        """See InterRepository.fetch()."""
2897
 
        from bzrlib.fetch import RepoFetcher
2898
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2899
 
               self.source, self.source._format, self.target, self.target._format)
2900
 
        f = RepoFetcher(to_repository=self.target,
2901
 
                            from_repository=self.source,
2902
 
                            last_revision=revision_id,
2903
 
                            pb=pb, find_ghosts=find_ghosts)
2904
 
 
2905
3471
    @needs_read_lock
2906
3472
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2907
3473
        """See InterRepository.missing_revision_ids()."""
2936
3502
        return self.source.revision_ids_to_search_result(result_set)
2937
3503
 
2938
3504
 
2939
 
class InterPackRepo(InterSameDataRepository):
2940
 
    """Optimised code paths between Pack based repositories."""
2941
 
 
2942
 
    @classmethod
2943
 
    def _get_repo_format_to_test(self):
2944
 
        from bzrlib.repofmt import pack_repo
2945
 
        return pack_repo.RepositoryFormatKnitPack1()
2946
 
 
2947
 
    @staticmethod
2948
 
    def is_compatible(source, target):
2949
 
        """Be compatible with known Pack formats.
2950
 
 
2951
 
        We don't test for the stores being of specific types because that
2952
 
        could lead to confusing results, and there is no need to be
2953
 
        overly general.
2954
 
        """
2955
 
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
2956
 
        try:
2957
 
            are_packs = (isinstance(source._format, RepositoryFormatPack) and
2958
 
                isinstance(target._format, RepositoryFormatPack))
2959
 
        except AttributeError:
2960
 
            return False
2961
 
        return are_packs and InterRepository._same_model(source, target)
2962
 
 
2963
 
    @needs_write_lock
2964
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2965
 
        """See InterRepository.fetch()."""
2966
 
        if (len(self.source._fallback_repositories) > 0 or
2967
 
            len(self.target._fallback_repositories) > 0):
2968
 
            # The pack layer is not aware of fallback repositories, so when
2969
 
            # fetching from a stacked repository or into a stacked repository
2970
 
            # we use the generic fetch logic which uses the VersionedFiles
2971
 
            # attributes on repository.
2972
 
            from bzrlib.fetch import RepoFetcher
2973
 
            fetcher = RepoFetcher(self.target, self.source, revision_id,
2974
 
                                  pb, find_ghosts)
2975
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2976
 
               self.source, self.source._format, self.target, self.target._format)
2977
 
        if revision_id is None:
2978
 
            # TODO:
2979
 
            # everything to do - use pack logic
2980
 
            # to fetch from all packs to one without
2981
 
            # inventory parsing etc, IFF nothing to be copied is in the target.
2982
 
            # till then:
2983
 
            source_revision_ids = frozenset(self.source.all_revision_ids())
2984
 
            revision_ids = source_revision_ids - \
2985
 
                frozenset(self.target_get_parent_map(source_revision_ids))
2986
 
            revision_keys = [(revid,) for revid in revision_ids]
2987
 
            target_pack_collection = self._get_target_pack_collection()
2988
 
            index = target_pack_collection.revision_index.combined_index
2989
 
            present_revision_ids = set(item[1][0] for item in
2990
 
                index.iter_entries(revision_keys))
2991
 
            revision_ids = set(revision_ids) - present_revision_ids
2992
 
            # implementing the TODO will involve:
2993
 
            # - detecting when all of a pack is selected
2994
 
            # - avoiding as much as possible pre-selection, so the
2995
 
            # more-core routines such as create_pack_from_packs can filter in
2996
 
            # a just-in-time fashion. (though having a HEADS list on a
2997
 
            # repository might make this a lot easier, because we could
2998
 
            # sensibly detect 'new revisions' without doing a full index scan.
2999
 
        elif _mod_revision.is_null(revision_id):
3000
 
            # nothing to do:
3001
 
            return (0, [])
3002
 
        else:
3003
 
            try:
3004
 
                revision_ids = self.search_missing_revision_ids(revision_id,
3005
 
                    find_ghosts=find_ghosts).get_keys()
3006
 
            except errors.NoSuchRevision:
3007
 
                raise errors.InstallFailed([revision_id])
3008
 
            if len(revision_ids) == 0:
3009
 
                return (0, [])
3010
 
        return self._pack(self.source, self.target, revision_ids)
3011
 
 
3012
 
    def _pack(self, source, target, revision_ids):
3013
 
        from bzrlib.repofmt.pack_repo import Packer
3014
 
        target_pack_collection = self._get_target_pack_collection()
3015
 
        packs = source._pack_collection.all_packs()
3016
 
        pack = Packer(target_pack_collection, packs, '.fetch',
3017
 
            revision_ids).pack()
3018
 
        if pack is not None:
3019
 
            target_pack_collection._save_pack_names()
3020
 
            copied_revs = pack.get_revision_count()
3021
 
            # Trigger an autopack. This may duplicate effort as we've just done
3022
 
            # a pack creation, but for now it is simpler to think about as
3023
 
            # 'upload data, then repack if needed'.
3024
 
            self._autopack()
3025
 
            return (copied_revs, [])
3026
 
        else:
3027
 
            return (0, [])
3028
 
 
3029
 
    def _autopack(self):
3030
 
        self.target._pack_collection.autopack()
3031
 
 
3032
 
    def _get_target_pack_collection(self):
3033
 
        return self.target._pack_collection
3034
 
 
3035
 
    @needs_read_lock
3036
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3037
 
        """See InterRepository.missing_revision_ids().
3038
 
 
3039
 
        :param find_ghosts: Find ghosts throughout the ancestry of
3040
 
            revision_id.
3041
 
        """
3042
 
        if not find_ghosts and revision_id is not None:
3043
 
            return self._walk_to_common_revisions([revision_id])
3044
 
        elif revision_id is not None:
3045
 
            # Find ghosts: search for revisions pointing from one repository to
3046
 
            # the other, and vice versa, anywhere in the history of revision_id.
3047
 
            graph = self.target_get_graph(other_repository=self.source)
3048
 
            searcher = graph._make_breadth_first_searcher([revision_id])
3049
 
            found_ids = set()
3050
 
            while True:
3051
 
                try:
3052
 
                    next_revs, ghosts = searcher.next_with_ghosts()
3053
 
                except StopIteration:
3054
 
                    break
3055
 
                if revision_id in ghosts:
3056
 
                    raise errors.NoSuchRevision(self.source, revision_id)
3057
 
                found_ids.update(next_revs)
3058
 
                found_ids.update(ghosts)
3059
 
            found_ids = frozenset(found_ids)
3060
 
            # Double query here: should be able to avoid this by changing the
3061
 
            # graph api further.
3062
 
            result_set = found_ids - frozenset(
3063
 
                self.target_get_parent_map(found_ids))
3064
 
        else:
3065
 
            source_ids = self.source.all_revision_ids()
3066
 
            # source_ids is the worst possible case we may need to pull.
3067
 
            # now we want to filter source_ids against what we actually
3068
 
            # have in target, but don't try to check for existence where we know
3069
 
            # we do not have a revision as that would be pointless.
3070
 
            target_ids = set(self.target.all_revision_ids())
3071
 
            result_set = set(source_ids).difference(target_ids)
3072
 
        return self.source.revision_ids_to_search_result(result_set)
3073
 
 
3074
 
 
3075
 
class InterDifferingSerializer(InterKnitRepo):
 
3505
class InterDifferingSerializer(InterRepository):
3076
3506
 
3077
3507
    @classmethod
3078
3508
    def _get_repo_format_to_test(self):
3081
3511
    @staticmethod
3082
3512
    def is_compatible(source, target):
3083
3513
        """Be compatible with Knit2 source and Knit3 target"""
3084
 
        if source.supports_rich_root() != target.supports_rich_root():
 
3514
        # This is redundant with format.check_conversion_target(), however that
 
3515
        # raises an exception, and we just want to say "False" as in we won't
 
3516
        # support converting between these formats.
 
3517
        if source.supports_rich_root() and not target.supports_rich_root():
3085
3518
            return False
3086
 
        # Ideally, we'd support fetching if the source had no tree references
3087
 
        # even if it supported them...
3088
 
        if (getattr(source, '_format.supports_tree_reference', False) and
3089
 
            not getattr(target, '_format.supports_tree_reference', False)):
 
3519
        if (source._format.supports_tree_reference
 
3520
            and not target._format.supports_tree_reference):
3090
3521
            return False
3091
3522
        return True
3092
3523
 
3109
3540
        deltas.sort()
3110
3541
        return deltas[0][1:]
3111
3542
 
 
3543
    def _get_parent_keys(self, root_key, parent_map):
 
3544
        """Get the parent keys for a given root id."""
 
3545
        root_id, rev_id = root_key
 
3546
        # Include direct parents of the revision, but only if they used
 
3547
        # the same root_id and are heads.
 
3548
        parent_keys = []
 
3549
        for parent_id in parent_map[rev_id]:
 
3550
            if parent_id == _mod_revision.NULL_REVISION:
 
3551
                continue
 
3552
            if parent_id not in self._revision_id_to_root_id:
 
3553
                # We probably didn't read this revision, go spend the
 
3554
                # extra effort to actually check
 
3555
                try:
 
3556
                    tree = self.source.revision_tree(parent_id)
 
3557
                except errors.NoSuchRevision:
 
3558
                    # Ghost, fill out _revision_id_to_root_id in case we
 
3559
                    # encounter this again.
 
3560
                    # But set parent_root_id to None since we don't really know
 
3561
                    parent_root_id = None
 
3562
                else:
 
3563
                    parent_root_id = tree.get_root_id()
 
3564
                self._revision_id_to_root_id[parent_id] = None
 
3565
            else:
 
3566
                parent_root_id = self._revision_id_to_root_id[parent_id]
 
3567
            if root_id == parent_root_id:
 
3568
                # With stacking we _might_ want to refer to a non-local
 
3569
                # revision, but this code path only applies when we have the
 
3570
                # full content available, so ghosts really are ghosts, not just
 
3571
                # the edge of local data.
 
3572
                parent_keys.append((parent_id,))
 
3573
            else:
 
3574
                # root_id may be in the parent anyway.
 
3575
                try:
 
3576
                    tree = self.source.revision_tree(parent_id)
 
3577
                except errors.NoSuchRevision:
 
3578
                    # ghost, can't refer to it.
 
3579
                    pass
 
3580
                else:
 
3581
                    try:
 
3582
                        parent_keys.append((tree.inventory[root_id].revision,))
 
3583
                    except errors.NoSuchId:
 
3584
                        # not in the tree
 
3585
                        pass
 
3586
        g = graph.Graph(self.source.revisions)
 
3587
        heads = g.heads(parent_keys)
 
3588
        selected_keys = []
 
3589
        for key in parent_keys:
 
3590
            if key in heads and key not in selected_keys:
 
3591
                selected_keys.append(key)
 
3592
        return tuple([(root_id,)+ key for key in selected_keys])
 
3593
 
 
3594
    def _new_root_data_stream(self, root_keys_to_create, parent_map):
 
3595
        for root_key in root_keys_to_create:
 
3596
            parent_keys = self._get_parent_keys(root_key, parent_map)
 
3597
            yield versionedfile.FulltextContentFactory(root_key,
 
3598
                parent_keys, None, '')
 
3599
 
3112
3600
    def _fetch_batch(self, revision_ids, basis_id, cache):
3113
3601
        """Fetch across a few revisions.
3114
3602
 
3122
3610
        # Walk though all revisions; get inventory deltas, copy referenced
3123
3611
        # texts that delta references, insert the delta, revision and
3124
3612
        # signature.
 
3613
        root_keys_to_create = set()
3125
3614
        text_keys = set()
3126
3615
        pending_deltas = []
3127
3616
        pending_revisions = []
3131
3620
            parent_ids = parent_map.get(current_revision_id, ())
3132
3621
            basis_id, delta = self._get_delta_for_revision(tree, parent_ids,
3133
3622
                                                           basis_id, cache)
 
3623
            if self._converting_to_rich_root:
 
3624
                self._revision_id_to_root_id[current_revision_id] = \
 
3625
                    tree.get_root_id()
3134
3626
            # Find text entries that need to be copied
3135
3627
            for old_path, new_path, file_id, entry in delta:
3136
3628
                if new_path is not None:
3137
 
                    if not (new_path or self.target.supports_rich_root()):
3138
 
                        # We don't copy the text for the root node unless the
3139
 
                        # target supports_rich_root.
3140
 
                        continue
3141
 
                    # TODO: Do we need:
3142
 
                    #       "if entry.revision == current_revision_id" ?
3143
 
                    if entry.revision == current_revision_id:
3144
 
                        text_keys.add((file_id, entry.revision))
 
3629
                    if not new_path:
 
3630
                        # This is the root
 
3631
                        if not self.target.supports_rich_root():
 
3632
                            # The target doesn't support rich root, so we don't
 
3633
                            # copy
 
3634
                            continue
 
3635
                        if self._converting_to_rich_root:
 
3636
                            # This can't be copied normally, we have to insert
 
3637
                            # it specially
 
3638
                            root_keys_to_create.add((file_id, entry.revision))
 
3639
                            continue
 
3640
                    text_keys.add((file_id, entry.revision))
3145
3641
            revision = self.source.get_revision(current_revision_id)
3146
3642
            pending_deltas.append((basis_id, delta,
3147
3643
                current_revision_id, revision.parent_ids))
3151
3647
        # Copy file texts
3152
3648
        from_texts = self.source.texts
3153
3649
        to_texts = self.target.texts
 
3650
        if root_keys_to_create:
 
3651
            root_stream = self._new_root_data_stream(root_keys_to_create,
 
3652
                                                     parent_map)
 
3653
            to_texts.insert_record_stream(root_stream)
3154
3654
        to_texts.insert_record_stream(from_texts.get_record_stream(
3155
3655
            text_keys, self.target._format._fetch_order,
3156
3656
            not self.target._format._fetch_uses_deltas))
3157
 
        # insert deltas
 
3657
        # insert inventory deltas
3158
3658
        for delta in pending_deltas:
3159
3659
            self.target.add_inventory_by_delta(*delta)
 
3660
        if self.target._fallback_repositories:
 
3661
            # Make sure this stacked repository has all the parent inventories
 
3662
            # for the new revisions that we are about to insert.  We do this
 
3663
            # before adding the revisions so that no revision is added until
 
3664
            # all the inventories it may depend on are added.
 
3665
            parent_ids = set()
 
3666
            revision_ids = set()
 
3667
            for revision in pending_revisions:
 
3668
                revision_ids.add(revision.revision_id)
 
3669
                parent_ids.update(revision.parent_ids)
 
3670
            parent_ids.difference_update(revision_ids)
 
3671
            parent_ids.discard(_mod_revision.NULL_REVISION)
 
3672
            parent_map = self.source.get_parent_map(parent_ids)
 
3673
            for parent_tree in self.source.revision_trees(parent_ids):
 
3674
                basis_id, delta = self._get_delta_for_revision(tree, parent_ids, basis_id, cache)
 
3675
                current_revision_id = parent_tree.get_revision_id()
 
3676
                parents_parents = parent_map[current_revision_id]
 
3677
                self.target.add_inventory_by_delta(
 
3678
                    basis_id, delta, current_revision_id, parents_parents)
3160
3679
        # insert signatures and revisions
3161
3680
        for revision in pending_revisions:
3162
3681
            try:
3182
3701
        cache = lru_cache.LRUCache(100)
3183
3702
        cache[basis_id] = basis_tree
3184
3703
        del basis_tree # We don't want to hang on to it here
 
3704
        hints = []
3185
3705
        for offset in range(0, len(revision_ids), batch_size):
3186
3706
            self.target.start_write_group()
3187
3707
            try:
3193
3713
                self.target.abort_write_group()
3194
3714
                raise
3195
3715
            else:
3196
 
                self.target.commit_write_group()
 
3716
                hint = self.target.commit_write_group()
 
3717
                if hint:
 
3718
                    hints.extend(hint)
 
3719
        if hints and self.target._format.pack_compresses:
 
3720
            self.target.pack(hint=hints)
3197
3721
        pb.update('Transferring revisions', len(revision_ids),
3198
3722
                  len(revision_ids))
3199
3723
 
3200
3724
    @needs_write_lock
3201
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
3725
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3726
            fetch_spec=None):
3202
3727
        """See InterRepository.fetch()."""
 
3728
        if fetch_spec is not None:
 
3729
            raise AssertionError("Not implemented yet...")
 
3730
        if (not self.source.supports_rich_root()
 
3731
            and self.target.supports_rich_root()):
 
3732
            self._converting_to_rich_root = True
 
3733
            self._revision_id_to_root_id = {}
 
3734
        else:
 
3735
            self._converting_to_rich_root = False
3203
3736
        revision_ids = self.target.search_missing_revision_ids(self.source,
3204
3737
            revision_id, find_ghosts=find_ghosts).get_keys()
3205
3738
        if not revision_ids:
3206
3739
            return 0, 0
3207
3740
        revision_ids = tsort.topo_sort(
3208
3741
            self.source.get_graph().get_parent_map(revision_ids))
 
3742
        if not revision_ids:
 
3743
            return 0, 0
 
3744
        # Walk though all revisions; get inventory deltas, copy referenced
 
3745
        # texts that delta references, insert the delta, revision and
 
3746
        # signature.
 
3747
        first_rev = self.source.get_revision(revision_ids[0])
3209
3748
        if pb is None:
3210
3749
            my_pb = ui.ui_factory.nested_progress_bar()
3211
3750
            pb = my_pb
3212
3751
        else:
 
3752
            symbol_versioning.warn(
 
3753
                symbol_versioning.deprecated_in((1, 14, 0))
 
3754
                % "pb parameter to fetch()")
3213
3755
            my_pb = None
3214
3756
        try:
3215
3757
            self._fetch_all_revisions(revision_ids, pb)
3241
3783
        return basis_id, basis_tree
3242
3784
 
3243
3785
 
3244
 
class InterOtherToRemote(InterRepository):
3245
 
    """An InterRepository that simply delegates to the 'real' InterRepository
3246
 
    calculated for (source, target._real_repository).
3247
 
    """
3248
 
 
3249
 
    _walk_to_common_revisions_batch_size = 50
3250
 
 
3251
 
    def __init__(self, source, target):
3252
 
        InterRepository.__init__(self, source, target)
3253
 
        self._real_inter = None
3254
 
 
3255
 
    @staticmethod
3256
 
    def is_compatible(source, target):
3257
 
        if isinstance(target, remote.RemoteRepository):
3258
 
            return True
3259
 
        return False
3260
 
 
3261
 
    def _ensure_real_inter(self):
3262
 
        if self._real_inter is None:
3263
 
            self.target._ensure_real()
3264
 
            real_target = self.target._real_repository
3265
 
            self._real_inter = InterRepository.get(self.source, real_target)
3266
 
            # Make _real_inter use the RemoteRepository for get_parent_map
3267
 
            self._real_inter.target_get_graph = self.target.get_graph
3268
 
            self._real_inter.target_get_parent_map = self.target.get_parent_map
3269
 
 
3270
 
    def copy_content(self, revision_id=None):
3271
 
        self._ensure_real_inter()
3272
 
        self._real_inter.copy_content(revision_id=revision_id)
3273
 
 
3274
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3275
 
        self._ensure_real_inter()
3276
 
        return self._real_inter.fetch(revision_id=revision_id, pb=pb,
3277
 
            find_ghosts=find_ghosts)
3278
 
 
3279
 
    @classmethod
3280
 
    def _get_repo_format_to_test(self):
3281
 
        return None
3282
 
 
3283
 
 
3284
 
class InterRemoteToOther(InterRepository):
3285
 
 
3286
 
    def __init__(self, source, target):
3287
 
        InterRepository.__init__(self, source, target)
3288
 
        self._real_inter = None
3289
 
 
3290
 
    @staticmethod
3291
 
    def is_compatible(source, target):
3292
 
        if not isinstance(source, remote.RemoteRepository):
3293
 
            return False
3294
 
        return InterRepository._same_model(source, target)
3295
 
 
3296
 
    def _ensure_real_inter(self):
3297
 
        if self._real_inter is None:
3298
 
            self.source._ensure_real()
3299
 
            real_source = self.source._real_repository
3300
 
            self._real_inter = InterRepository.get(real_source, self.target)
3301
 
 
3302
 
    @needs_write_lock
3303
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3304
 
        """See InterRepository.fetch()."""
3305
 
        # Always fetch using the generic streaming fetch code, to allow
3306
 
        # streaming fetching from remote servers.
3307
 
        from bzrlib.fetch import RepoFetcher
3308
 
        fetcher = RepoFetcher(self.target, self.source, revision_id,
3309
 
                              pb, find_ghosts)
3310
 
 
3311
 
    def copy_content(self, revision_id=None):
3312
 
        self._ensure_real_inter()
3313
 
        self._real_inter.copy_content(revision_id=revision_id)
3314
 
 
3315
 
    @classmethod
3316
 
    def _get_repo_format_to_test(self):
3317
 
        return None
3318
 
 
3319
 
 
3320
 
 
3321
 
class InterPackToRemotePack(InterPackRepo):
3322
 
    """A specialisation of InterPackRepo for a target that is a
3323
 
    RemoteRepository.
3324
 
 
3325
 
    This will use the get_parent_map RPC rather than plain readvs, and also
3326
 
    uses an RPC for autopacking.
3327
 
    """
3328
 
 
3329
 
    _walk_to_common_revisions_batch_size = 50
3330
 
 
3331
 
    @staticmethod
3332
 
    def is_compatible(source, target):
3333
 
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
3334
 
        if isinstance(source._format, RepositoryFormatPack):
3335
 
            if isinstance(target, remote.RemoteRepository):
3336
 
                target._format._ensure_real()
3337
 
                if isinstance(target._format._custom_format,
3338
 
                              RepositoryFormatPack):
3339
 
                    if InterRepository._same_model(source, target):
3340
 
                        return True
3341
 
        return False
3342
 
 
3343
 
    def _autopack(self):
3344
 
        self.target.autopack()
3345
 
 
3346
 
    @needs_write_lock
3347
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3348
 
        """See InterRepository.fetch()."""
3349
 
        # Always fetch using the generic streaming fetch code, to allow
3350
 
        # streaming fetching into remote servers.
3351
 
        from bzrlib.fetch import RepoFetcher
3352
 
        fetcher = RepoFetcher(self.target, self.source, revision_id,
3353
 
                              pb, find_ghosts)
3354
 
 
3355
 
    def _get_target_pack_collection(self):
3356
 
        return self.target._real_repository._pack_collection
3357
 
 
3358
 
    @classmethod
3359
 
    def _get_repo_format_to_test(self):
3360
 
        return None
3361
 
 
3362
 
 
3363
3786
InterRepository.register_optimiser(InterDifferingSerializer)
3364
3787
InterRepository.register_optimiser(InterSameDataRepository)
3365
3788
InterRepository.register_optimiser(InterWeaveRepo)
3366
3789
InterRepository.register_optimiser(InterKnitRepo)
3367
 
InterRepository.register_optimiser(InterPackRepo)
3368
 
InterRepository.register_optimiser(InterOtherToRemote)
3369
 
InterRepository.register_optimiser(InterRemoteToOther)
3370
 
InterRepository.register_optimiser(InterPackToRemotePack)
3371
3790
 
3372
3791
 
3373
3792
class CopyConverter(object):
3454
3873
 
3455
3874
class _VersionedFileChecker(object):
3456
3875
 
3457
 
    def __init__(self, repository):
 
3876
    def __init__(self, repository, text_key_references=None):
3458
3877
        self.repository = repository
3459
 
        self.text_index = self.repository._generate_text_key_index()
 
3878
        self.text_index = self.repository._generate_text_key_index(
 
3879
            text_key_references=text_key_references)
3460
3880
 
3461
3881
    def calculate_file_version_parents(self, text_key):
3462
3882
        """Calculate the correct parents for a file version according to
3550
3970
        try:
3551
3971
            if resume_tokens:
3552
3972
                self.target_repo.resume_write_group(resume_tokens)
 
3973
                is_resume = True
3553
3974
            else:
3554
3975
                self.target_repo.start_write_group()
 
3976
                is_resume = False
3555
3977
            try:
3556
3978
                # locked_insert_stream performs a commit|suspend.
3557
 
                return self._locked_insert_stream(stream, src_format)
 
3979
                return self._locked_insert_stream(stream, src_format, is_resume)
3558
3980
            except:
3559
3981
                self.target_repo.abort_write_group(suppress_errors=True)
3560
3982
                raise
3561
3983
        finally:
3562
3984
            self.target_repo.unlock()
3563
3985
 
3564
 
    def _locked_insert_stream(self, stream, src_format):
 
3986
    def _locked_insert_stream(self, stream, src_format, is_resume):
3565
3987
        to_serializer = self.target_repo._format._serializer
3566
3988
        src_serializer = src_format._serializer
 
3989
        new_pack = None
 
3990
        if to_serializer == src_serializer:
 
3991
            # If serializers match and the target is a pack repository, set the
 
3992
            # write cache size on the new pack.  This avoids poor performance
 
3993
            # on transports where append is unbuffered (such as
 
3994
            # RemoteTransport).  This is safe to do because nothing should read
 
3995
            # back from the target repository while a stream with matching
 
3996
            # serialization is being inserted.
 
3997
            # The exception is that a delta record from the source that should
 
3998
            # be a fulltext may need to be expanded by the target (see
 
3999
            # test_fetch_revisions_with_deltas_into_pack); but we take care to
 
4000
            # explicitly flush any buffered writes first in that rare case.
 
4001
            try:
 
4002
                new_pack = self.target_repo._pack_collection._new_pack
 
4003
            except AttributeError:
 
4004
                # Not a pack repository
 
4005
                pass
 
4006
            else:
 
4007
                new_pack.set_write_cache_size(1024*1024)
3567
4008
        for substream_type, substream in stream:
3568
4009
            if substream_type == 'texts':
3569
4010
                self.target_repo.texts.insert_record_stream(substream)
3574
4015
                else:
3575
4016
                    self._extract_and_insert_inventories(
3576
4017
                        substream, src_serializer)
 
4018
            elif substream_type == 'chk_bytes':
 
4019
                # XXX: This doesn't support conversions, as it assumes the
 
4020
                #      conversion was done in the fetch code.
 
4021
                self.target_repo.chk_bytes.insert_record_stream(substream)
3577
4022
            elif substream_type == 'revisions':
3578
4023
                # This may fallback to extract-and-insert more often than
3579
4024
                # required if the serializers are different only in terms of
3588
4033
                self.target_repo.signatures.insert_record_stream(substream)
3589
4034
            else:
3590
4035
                raise AssertionError('kaboom! %s' % (substream_type,))
 
4036
        # Done inserting data, and the missing_keys calculations will try to
 
4037
        # read back from the inserted data, so flush the writes to the new pack
 
4038
        # (if this is pack format).
 
4039
        if new_pack is not None:
 
4040
            new_pack._write_data('', flush=True)
 
4041
        # Find all the new revisions (including ones from resume_tokens)
 
4042
        missing_keys = self.target_repo.get_missing_parent_inventories(
 
4043
            check_for_missing_texts=is_resume)
3591
4044
        try:
3592
 
            missing_keys = set()
3593
4045
            for prefix, versioned_file in (
3594
4046
                ('texts', self.target_repo.texts),
3595
4047
                ('inventories', self.target_repo.inventories),
3596
4048
                ('revisions', self.target_repo.revisions),
3597
4049
                ('signatures', self.target_repo.signatures),
 
4050
                ('chk_bytes', self.target_repo.chk_bytes),
3598
4051
                ):
 
4052
                if versioned_file is None:
 
4053
                    continue
3599
4054
                missing_keys.update((prefix,) + key for key in
3600
4055
                    versioned_file.get_missing_compression_parent_keys())
3601
4056
        except NotImplementedError:
3610
4065
                # missing keys can handle suspending a write group).
3611
4066
                write_group_tokens = self.target_repo.suspend_write_group()
3612
4067
                return write_group_tokens, missing_keys
3613
 
        self.target_repo.commit_write_group()
 
4068
        hint = self.target_repo.commit_write_group()
 
4069
        if (to_serializer != src_serializer and
 
4070
            self.target_repo._format.pack_compresses):
 
4071
            self.target_repo.pack(hint=hint)
3614
4072
        return [], set()
3615
4073
 
3616
4074
    def _extract_and_insert_inventories(self, substream, serializer):
3728
4186
                # know for unselected inventories whether all their required
3729
4187
                # texts are present in the other repository - it could be
3730
4188
                # corrupt.
3731
 
                yield ('inventories', from_weave.get_record_stream(
3732
 
                    [(rev_id,) for rev_id in revs],
3733
 
                    self.inventory_fetch_order(),
3734
 
                    not self.delta_on_metadata()))
 
4189
                for info in self._get_inventory_stream(revs):
 
4190
                    yield info
3735
4191
            elif knit_kind == "signatures":
3736
4192
                # Nothing to do here; this will be taken care of when
3737
4193
                # _fetch_revision_texts happens.
3750
4206
        keys['texts'] = set()
3751
4207
        keys['revisions'] = set()
3752
4208
        keys['inventories'] = set()
 
4209
        keys['chk_bytes'] = set()
3753
4210
        keys['signatures'] = set()
3754
4211
        for key in missing_keys:
3755
4212
            keys[key[0]].add(key[1:])
3762
4219
                    keys['revisions'],))
3763
4220
        for substream_kind, keys in keys.iteritems():
3764
4221
            vf = getattr(self.from_repository, substream_kind)
 
4222
            if vf is None and keys:
 
4223
                    raise AssertionError(
 
4224
                        "cannot fill in keys for a versioned file we don't"
 
4225
                        " have: %s needs %s" % (substream_kind, keys))
 
4226
            if not keys:
 
4227
                # No need to stream something we don't have
 
4228
                continue
3765
4229
            # Ask for full texts always so that we don't need more round trips
3766
4230
            # after this stream.
3767
 
            stream = vf.get_record_stream(keys,
3768
 
                self.to_format._fetch_order, True)
 
4231
            # Some of the missing keys are genuinely ghosts, so filter absent
 
4232
            # records. The Sink is responsible for doing another check to
 
4233
            # ensure that ghosts don't introduce missing data for future
 
4234
            # fetches.
 
4235
            stream = versionedfile.filter_absent(vf.get_record_stream(keys,
 
4236
                self.to_format._fetch_order, True))
3769
4237
            yield substream_kind, stream
3770
4238
 
3771
4239
    def inventory_fetch_order(self):
3778
4246
        return (not self.from_repository._format.rich_root_data and
3779
4247
            self.to_format.rich_root_data)
3780
4248
 
 
4249
    def _get_inventory_stream(self, revision_ids):
 
4250
        from_format = self.from_repository._format
 
4251
        if (from_format.supports_chks and self.to_format.supports_chks
 
4252
            and (from_format._serializer == self.to_format._serializer)):
 
4253
            # Both sides support chks, and they use the same serializer, so it
 
4254
            # is safe to transmit the chk pages and inventory pages across
 
4255
            # as-is.
 
4256
            return self._get_chk_inventory_stream(revision_ids)
 
4257
        elif (not from_format.supports_chks):
 
4258
            # Source repository doesn't support chks. So we can transmit the
 
4259
            # inventories 'as-is' and either they are just accepted on the
 
4260
            # target, or the Sink will properly convert it.
 
4261
            return self._get_simple_inventory_stream(revision_ids)
 
4262
        else:
 
4263
            # XXX: Hack to make not-chk->chk fetch: copy the inventories as
 
4264
            #      inventories. Note that this should probably be done somehow
 
4265
            #      as part of bzrlib.repository.StreamSink. Except JAM couldn't
 
4266
            #      figure out how a non-chk repository could possibly handle
 
4267
            #      deserializing an inventory stream from a chk repo, as it
 
4268
            #      doesn't have a way to understand individual pages.
 
4269
            return self._get_convertable_inventory_stream(revision_ids)
 
4270
 
 
4271
    def _get_simple_inventory_stream(self, revision_ids):
 
4272
        from_weave = self.from_repository.inventories
 
4273
        yield ('inventories', from_weave.get_record_stream(
 
4274
            [(rev_id,) for rev_id in revision_ids],
 
4275
            self.inventory_fetch_order(),
 
4276
            not self.delta_on_metadata()))
 
4277
 
 
4278
    def _get_chk_inventory_stream(self, revision_ids):
 
4279
        """Fetch the inventory texts, along with the associated chk maps."""
 
4280
        # We want an inventory outside of the search set, so that we can filter
 
4281
        # out uninteresting chk pages. For now we use
 
4282
        # _find_revision_outside_set, but if we had a Search with cut_revs, we
 
4283
        # could use that instead.
 
4284
        start_rev_id = self.from_repository._find_revision_outside_set(
 
4285
                            revision_ids)
 
4286
        start_rev_key = (start_rev_id,)
 
4287
        inv_keys_to_fetch = [(rev_id,) for rev_id in revision_ids]
 
4288
        if start_rev_id != _mod_revision.NULL_REVISION:
 
4289
            inv_keys_to_fetch.append((start_rev_id,))
 
4290
        # Any repo that supports chk_bytes must also support out-of-order
 
4291
        # insertion. At least, that is how we expect it to work
 
4292
        # We use get_record_stream instead of iter_inventories because we want
 
4293
        # to be able to insert the stream as well. We could instead fetch
 
4294
        # allowing deltas, and then iter_inventories, but we don't know whether
 
4295
        # source or target is more 'local' anway.
 
4296
        inv_stream = self.from_repository.inventories.get_record_stream(
 
4297
            inv_keys_to_fetch, 'unordered',
 
4298
            True) # We need them as full-texts so we can find their references
 
4299
        uninteresting_chk_roots = set()
 
4300
        interesting_chk_roots = set()
 
4301
        def filter_inv_stream(inv_stream):
 
4302
            for idx, record in enumerate(inv_stream):
 
4303
                ### child_pb.update('fetch inv', idx, len(inv_keys_to_fetch))
 
4304
                bytes = record.get_bytes_as('fulltext')
 
4305
                chk_inv = inventory.CHKInventory.deserialise(
 
4306
                    self.from_repository.chk_bytes, bytes, record.key)
 
4307
                if record.key == start_rev_key:
 
4308
                    uninteresting_chk_roots.add(chk_inv.id_to_entry.key())
 
4309
                    p_id_map = chk_inv.parent_id_basename_to_file_id
 
4310
                    if p_id_map is not None:
 
4311
                        uninteresting_chk_roots.add(p_id_map.key())
 
4312
                else:
 
4313
                    yield record
 
4314
                    interesting_chk_roots.add(chk_inv.id_to_entry.key())
 
4315
                    p_id_map = chk_inv.parent_id_basename_to_file_id
 
4316
                    if p_id_map is not None:
 
4317
                        interesting_chk_roots.add(p_id_map.key())
 
4318
        ### pb.update('fetch inventory', 0, 2)
 
4319
        yield ('inventories', filter_inv_stream(inv_stream))
 
4320
        # Now that we have worked out all of the interesting root nodes, grab
 
4321
        # all of the interesting pages and insert them
 
4322
        ### pb.update('fetch inventory', 1, 2)
 
4323
        interesting = chk_map.iter_interesting_nodes(
 
4324
            self.from_repository.chk_bytes, interesting_chk_roots,
 
4325
            uninteresting_chk_roots)
 
4326
        def to_stream_adapter():
 
4327
            """Adapt the iter_interesting_nodes result to a single stream.
 
4328
 
 
4329
            iter_interesting_nodes returns records as it processes them, along
 
4330
            with keys. However, we only want to return the records themselves.
 
4331
            """
 
4332
            for record, items in interesting:
 
4333
                if record is not None:
 
4334
                    yield record
 
4335
        # XXX: We could instead call get_record_stream(records.keys())
 
4336
        #      ATM, this will always insert the records as fulltexts, and
 
4337
        #      requires that you can hang on to records once you have gone
 
4338
        #      on to the next one. Further, it causes the target to
 
4339
        #      recompress the data. Testing shows it to be faster than
 
4340
        #      requesting the records again, though.
 
4341
        yield ('chk_bytes', to_stream_adapter())
 
4342
        ### pb.update('fetch inventory', 2, 2)
 
4343
 
 
4344
    def _get_convertable_inventory_stream(self, revision_ids):
 
4345
        # XXX: One of source or target is using chks, and they don't have
 
4346
        #      compatible serializations. The StreamSink code expects to be
 
4347
        #      able to convert on the target, so we need to put
 
4348
        #      bytes-on-the-wire that can be converted
 
4349
        yield ('inventories', self._stream_invs_as_fulltexts(revision_ids))
 
4350
 
 
4351
    def _stream_invs_as_fulltexts(self, revision_ids):
 
4352
        from_repo = self.from_repository
 
4353
        from_serializer = from_repo._format._serializer
 
4354
        revision_keys = [(rev_id,) for rev_id in revision_ids]
 
4355
        parent_map = from_repo.inventories.get_parent_map(revision_keys)
 
4356
        for inv in self.from_repository.iter_inventories(revision_ids):
 
4357
            # XXX: This is a bit hackish, but it works. Basically,
 
4358
            #      CHKSerializer 'accidentally' supports
 
4359
            #      read/write_inventory_to_string, even though that is never
 
4360
            #      the format that is stored on disk. It *does* give us a
 
4361
            #      single string representation for an inventory, so live with
 
4362
            #      it for now.
 
4363
            #      This would be far better if we had a 'serialized inventory
 
4364
            #      delta' form. Then we could use 'inventory._make_delta', and
 
4365
            #      transmit that. This would both be faster to generate, and
 
4366
            #      result in fewer bytes-on-the-wire.
 
4367
            as_bytes = from_serializer.write_inventory_to_string(inv)
 
4368
            key = (inv.revision_id,)
 
4369
            parent_keys = parent_map.get(key, ())
 
4370
            yield versionedfile.FulltextContentFactory(
 
4371
                key, parent_keys, None, as_bytes)
 
4372
 
 
4373
 
 
4374
def _iter_for_revno(repo, partial_history_cache, stop_index=None,
 
4375
                    stop_revision=None):
 
4376
    """Extend the partial history to include a given index
 
4377
 
 
4378
    If a stop_index is supplied, stop when that index has been reached.
 
4379
    If a stop_revision is supplied, stop when that revision is
 
4380
    encountered.  Otherwise, stop when the beginning of history is
 
4381
    reached.
 
4382
 
 
4383
    :param stop_index: The index which should be present.  When it is
 
4384
        present, history extension will stop.
 
4385
    :param stop_revision: The revision id which should be present.  When
 
4386
        it is encountered, history extension will stop.
 
4387
    """
 
4388
    start_revision = partial_history_cache[-1]
 
4389
    iterator = repo.iter_reverse_revision_history(start_revision)
 
4390
    try:
 
4391
        #skip the last revision in the list
 
4392
        iterator.next()
 
4393
        while True:
 
4394
            if (stop_index is not None and
 
4395
                len(partial_history_cache) > stop_index):
 
4396
                break
 
4397
            if partial_history_cache[-1] == stop_revision:
 
4398
                break
 
4399
            revision_id = iterator.next()
 
4400
            partial_history_cache.append(revision_id)
 
4401
    except StopIteration:
 
4402
        # No more history
 
4403
        return
 
4404