/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Matt Nordhoff
  • Date: 2009-04-04 02:50:01 UTC
  • mfrom: (4253 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4256.
  • Revision ID: mnordhoff@mattnordhoff.com-20090404025001-z1403k0tatmc8l91
Merge bzr.dev, fixing conflicts.

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(), """
34
34
    lockdir,
35
35
    lru_cache,
36
36
    osutils,
37
 
    remote,
38
37
    revision as _mod_revision,
39
38
    symbol_versioning,
40
39
    tsort,
47
46
from bzrlib.testament import Testament
48
47
""")
49
48
 
50
 
from bzrlib import registry
51
49
from bzrlib.decorators import needs_read_lock, needs_write_lock
52
50
from bzrlib.inter import InterObject
53
 
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
 
51
from bzrlib.inventory import (
 
52
    Inventory,
 
53
    InventoryDirectory,
 
54
    ROOT_ID,
 
55
    entry_factory,
 
56
    )
 
57
from bzrlib import registry
54
58
from bzrlib.symbol_versioning import (
55
59
        deprecated_method,
56
 
        one_one,
57
 
        one_two,
58
 
        one_six,
59
60
        )
60
61
from bzrlib.trace import (
61
62
    log_exception_quietly, note, mutter, mutter_callsite, warning)
126
127
        # valid. Callers that will call record_delete() should call
127
128
        # .will_record_deletes() to indicate that.
128
129
        self._recording_deletes = False
 
130
        # memo'd check for no-op commits.
 
131
        self._any_changes = False
 
132
 
 
133
    def any_changes(self):
 
134
        """Return True if any entries were changed.
 
135
        
 
136
        This includes merge-only changes. It is the core for the --unchanged
 
137
        detection in commit.
 
138
 
 
139
        :return: True if any changes have occured.
 
140
        """
 
141
        return self._any_changes
129
142
 
130
143
    def _validate_unicode_text(self, text, context):
131
144
        """Verify things like commit messages don't have bogus characters."""
176
189
        deserializing the inventory, while we already have a copy in
177
190
        memory.
178
191
        """
 
192
        if self.new_inventory is None:
 
193
            self.new_inventory = self.repository.get_inventory(
 
194
                self._new_revision_id)
179
195
        return RevisionTree(self.repository, self.new_inventory,
180
 
                            self._new_revision_id)
 
196
            self._new_revision_id)
181
197
 
182
198
    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
 
            )
 
199
        """Tell the builder that the inventory is finished.
 
200
        
 
201
        :return: The inventory id in the repository, which can be used with
 
202
            repository.get_inventory.
 
203
        """
 
204
        if self.new_inventory is None:
 
205
            # an inventory delta was accumulated without creating a new
 
206
            # inventory.
 
207
            basis_id = self.basis_delta_revision
 
208
            self.inv_sha1 = self.repository.add_inventory_by_delta(
 
209
                basis_id, self._basis_delta, self._new_revision_id,
 
210
                self.parents)
 
211
        else:
 
212
            if self.new_inventory.root is None:
 
213
                raise AssertionError('Root entry should be supplied to'
 
214
                    ' record_entry_contents, as of bzr 0.10.')
 
215
                self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
 
216
            self.new_inventory.revision_id = self._new_revision_id
 
217
            self.inv_sha1 = self.repository.add_inventory(
 
218
                self._new_revision_id,
 
219
                self.new_inventory,
 
220
                self.parents
 
221
                )
 
222
        return self._new_revision_id
194
223
 
195
224
    def _gen_revision_id(self):
196
225
        """Return new revision-id."""
199
228
 
200
229
    def _generate_revision_if_needed(self):
201
230
        """Create a revision id if None was supplied.
202
 
        
 
231
 
203
232
        If the repository can not support user-specified revision ids
204
233
        they should override this function and raise CannotSetRevisionId
205
234
        if _new_revision_id is not None.
233
262
        # _new_revision_id
234
263
        ie.revision = self._new_revision_id
235
264
 
 
265
    def _require_root_change(self, tree):
 
266
        """Enforce an appropriate root object change.
 
267
 
 
268
        This is called once when record_iter_changes is called, if and only if
 
269
        the root was not in the delta calculated by record_iter_changes.
 
270
 
 
271
        :param tree: The tree which is being committed.
 
272
        """
 
273
        # NB: if there are no parents then this method is not called, so no
 
274
        # need to guard on parents having length.
 
275
        entry = entry_factory['directory'](tree.path2id(''), '',
 
276
            None)
 
277
        entry.revision = self._new_revision_id
 
278
        self._basis_delta.append(('', '', entry.file_id, entry))
 
279
 
236
280
    def _get_delta(self, ie, basis_inv, path):
237
281
        """Get a delta against the basis inventory for ie."""
238
282
        if ie.file_id not in basis_inv:
280
324
            raise AssertionError("recording deletes not activated.")
281
325
        delta = (path, None, file_id, None)
282
326
        self._basis_delta.append(delta)
 
327
        self._any_changes = True
283
328
        return delta
284
329
 
285
330
    def will_record_deletes(self):
290
335
        builder.record_delete().
291
336
        """
292
337
        self._recording_deletes = True
 
338
        try:
 
339
            basis_id = self.parents[0]
 
340
        except IndexError:
 
341
            basis_id = _mod_revision.NULL_REVISION
 
342
        self.basis_delta_revision = basis_id
293
343
 
294
344
    def record_entry_contents(self, ie, parent_invs, path, tree,
295
345
        content_summary):
301
351
        :param parent_invs: The inventories of the parent revisions of the
302
352
            commit.
303
353
        :param path: The path the entry is at in the tree.
304
 
        :param tree: The tree which contains this entry and should be used to 
 
354
        :param tree: The tree which contains this entry and should be used to
305
355
            obtain content.
306
356
        :param content_summary: Summary data from the tree about the paths
307
357
            content - stat, length, exec, sha/link target. This is only
497
547
        else:
498
548
            raise NotImplementedError('unknown kind')
499
549
        ie.revision = self._new_revision_id
 
550
        self._any_changes = True
500
551
        return self._get_delta(ie, basis_inv, path), True, fingerprint
501
552
 
 
553
    def record_iter_changes(self, tree, basis_revision_id, iter_changes,
 
554
        _entry_factory=entry_factory):
 
555
        """Record a new tree via iter_changes.
 
556
 
 
557
        :param tree: The tree to obtain text contents from for changed objects.
 
558
        :param basis_revision_id: The revision id of the tree the iter_changes
 
559
            has been generated against. Currently assumed to be the same
 
560
            as self.parents[0] - if it is not, errors may occur.
 
561
        :param iter_changes: An iter_changes iterator with the changes to apply
 
562
            to basis_revision_id. The iterator must not include any items with
 
563
            a current kind of None - missing items must be either filtered out
 
564
            or errored-on beefore record_iter_changes sees the item.
 
565
        :param _entry_factory: Private method to bind entry_factory locally for
 
566
            performance.
 
567
        :return: A generator of (file_id, relpath, fs_hash) tuples for use with
 
568
            tree._observed_sha1.
 
569
        """
 
570
        # Create an inventory delta based on deltas between all the parents and
 
571
        # deltas between all the parent inventories. We use inventory delta's 
 
572
        # between the inventory objects because iter_changes masks
 
573
        # last-changed-field only changes.
 
574
        # Working data:
 
575
        # file_id -> change map, change is fileid, paths, changed, versioneds,
 
576
        # parents, names, kinds, executables
 
577
        merged_ids = {}
 
578
        # {file_id -> revision_id -> inventory entry, for entries in parent
 
579
        # trees that are not parents[0]
 
580
        parent_entries = {}
 
581
        ghost_basis = False
 
582
        try:
 
583
            revtrees = list(self.repository.revision_trees(self.parents))
 
584
        except errors.NoSuchRevision:
 
585
            # one or more ghosts, slow path.
 
586
            revtrees = []
 
587
            for revision_id in self.parents:
 
588
                try:
 
589
                    revtrees.append(self.repository.revision_tree(revision_id))
 
590
                except errors.NoSuchRevision:
 
591
                    if not revtrees:
 
592
                        basis_revision_id = _mod_revision.NULL_REVISION
 
593
                        ghost_basis = True
 
594
                    revtrees.append(self.repository.revision_tree(
 
595
                        _mod_revision.NULL_REVISION))
 
596
        # The basis inventory from a repository 
 
597
        if revtrees:
 
598
            basis_inv = revtrees[0].inventory
 
599
        else:
 
600
            basis_inv = self.repository.revision_tree(
 
601
                _mod_revision.NULL_REVISION).inventory
 
602
        if len(self.parents) > 0:
 
603
            if basis_revision_id != self.parents[0] and not ghost_basis:
 
604
                raise Exception(
 
605
                    "arbitrary basis parents not yet supported with merges")
 
606
            for revtree in revtrees[1:]:
 
607
                for change in revtree.inventory._make_delta(basis_inv):
 
608
                    if change[1] is None:
 
609
                        # Not present in this parent.
 
610
                        continue
 
611
                    if change[2] not in merged_ids:
 
612
                        if change[0] is not None:
 
613
                            basis_entry = basis_inv[change[2]]
 
614
                            merged_ids[change[2]] = [
 
615
                                # basis revid
 
616
                                basis_entry.revision,
 
617
                                # new tree revid
 
618
                                change[3].revision]
 
619
                            parent_entries[change[2]] = {
 
620
                                # basis parent
 
621
                                basis_entry.revision:basis_entry,
 
622
                                # this parent 
 
623
                                change[3].revision:change[3],
 
624
                                }
 
625
                        else:
 
626
                            merged_ids[change[2]] = [change[3].revision]
 
627
                            parent_entries[change[2]] = {change[3].revision:change[3]}
 
628
                    else:
 
629
                        merged_ids[change[2]].append(change[3].revision)
 
630
                        parent_entries[change[2]][change[3].revision] = change[3]
 
631
        else:
 
632
            merged_ids = {}
 
633
        # Setup the changes from the tree:
 
634
        # changes maps file_id -> (change, [parent revision_ids])
 
635
        changes= {}
 
636
        for change in iter_changes:
 
637
            # This probably looks up in basis_inv way to much.
 
638
            if change[1][0] is not None:
 
639
                head_candidate = [basis_inv[change[0]].revision]
 
640
            else:
 
641
                head_candidate = []
 
642
            changes[change[0]] = change, merged_ids.get(change[0],
 
643
                head_candidate)
 
644
        unchanged_merged = set(merged_ids) - set(changes)
 
645
        # Extend the changes dict with synthetic changes to record merges of
 
646
        # texts.
 
647
        for file_id in unchanged_merged:
 
648
            # Record a merged version of these items that did not change vs the
 
649
            # basis. This can be either identical parallel changes, or a revert
 
650
            # of a specific file after a merge. The recorded content will be
 
651
            # that of the current tree (which is the same as the basis), but
 
652
            # the per-file graph will reflect a merge.
 
653
            # NB:XXX: We are reconstructing path information we had, this
 
654
            # should be preserved instead.
 
655
            # inv delta  change: (file_id, (path_in_source, path_in_target),
 
656
            #   changed_content, versioned, parent, name, kind,
 
657
            #   executable)
 
658
            try:
 
659
                basis_entry = basis_inv[file_id]
 
660
            except errors.NoSuchId:
 
661
                # a change from basis->some_parents but file_id isn't in basis
 
662
                # so was new in the merge, which means it must have changed
 
663
                # from basis -> current, and as it hasn't the add was reverted
 
664
                # by the user. So we discard this change.
 
665
                pass
 
666
            else:
 
667
                change = (file_id,
 
668
                    (basis_inv.id2path(file_id), tree.id2path(file_id)),
 
669
                    False, (True, True),
 
670
                    (basis_entry.parent_id, basis_entry.parent_id),
 
671
                    (basis_entry.name, basis_entry.name),
 
672
                    (basis_entry.kind, basis_entry.kind),
 
673
                    (basis_entry.executable, basis_entry.executable))
 
674
                changes[file_id] = (change, merged_ids[file_id])
 
675
        # changes contains tuples with the change and a set of inventory
 
676
        # candidates for the file.
 
677
        # inv delta is:
 
678
        # old_path, new_path, file_id, new_inventory_entry
 
679
        seen_root = False # Is the root in the basis delta?
 
680
        inv_delta = self._basis_delta
 
681
        modified_rev = self._new_revision_id
 
682
        for change, head_candidates in changes.values():
 
683
            if change[3][1]: # versioned in target.
 
684
                # Several things may be happening here:
 
685
                # We may have a fork in the per-file graph
 
686
                #  - record a change with the content from tree
 
687
                # We may have a change against < all trees  
 
688
                #  - carry over the tree that hasn't changed
 
689
                # We may have a change against all trees
 
690
                #  - record the change with the content from tree
 
691
                kind = change[6][1]
 
692
                file_id = change[0]
 
693
                entry = _entry_factory[kind](file_id, change[5][1],
 
694
                    change[4][1])
 
695
                head_set = self._heads(change[0], set(head_candidates))
 
696
                heads = []
 
697
                # Preserve ordering.
 
698
                for head_candidate in head_candidates:
 
699
                    if head_candidate in head_set:
 
700
                        heads.append(head_candidate)
 
701
                        head_set.remove(head_candidate)
 
702
                carried_over = False
 
703
                if len(heads) == 1:
 
704
                    # Could be a carry-over situation:
 
705
                    parent_entry_revs = parent_entries.get(file_id, None)
 
706
                    if parent_entry_revs:
 
707
                        parent_entry = parent_entry_revs.get(heads[0], None)
 
708
                    else:
 
709
                        parent_entry = None
 
710
                    if parent_entry is None:
 
711
                        # The parent iter_changes was called against is the one
 
712
                        # that is the per-file head, so any change is relevant
 
713
                        # iter_changes is valid.
 
714
                        carry_over_possible = False
 
715
                    else:
 
716
                        # could be a carry over situation
 
717
                        # A change against the basis may just indicate a merge,
 
718
                        # we need to check the content against the source of the
 
719
                        # merge to determine if it was changed after the merge
 
720
                        # or carried over.
 
721
                        if (parent_entry.kind != entry.kind or
 
722
                            parent_entry.parent_id != entry.parent_id or
 
723
                            parent_entry.name != entry.name):
 
724
                            # Metadata common to all entries has changed
 
725
                            # against per-file parent
 
726
                            carry_over_possible = False
 
727
                        else:
 
728
                            carry_over_possible = True
 
729
                        # per-type checks for changes against the parent_entry
 
730
                        # are done below.
 
731
                else:
 
732
                    # Cannot be a carry-over situation
 
733
                    carry_over_possible = False
 
734
                # Populate the entry in the delta
 
735
                if kind == 'file':
 
736
                    # XXX: There is still a small race here: If someone reverts the content of a file
 
737
                    # after iter_changes examines and decides it has changed,
 
738
                    # we will unconditionally record a new version even if some
 
739
                    # other process reverts it while commit is running (with
 
740
                    # the revert happening after iter_changes did it's
 
741
                    # examination).
 
742
                    if change[7][1]:
 
743
                        entry.executable = True
 
744
                    else:
 
745
                        entry.executable = False
 
746
                    if (carry_over_possible and 
 
747
                        parent_entry.executable == entry.executable):
 
748
                            # Check the file length, content hash after reading
 
749
                            # the file.
 
750
                            nostore_sha = parent_entry.text_sha1
 
751
                    else:
 
752
                        nostore_sha = None
 
753
                    file_obj, stat_value = tree.get_file_with_stat(file_id, change[1][1])
 
754
                    try:
 
755
                        lines = file_obj.readlines()
 
756
                    finally:
 
757
                        file_obj.close()
 
758
                    try:
 
759
                        entry.text_sha1, entry.text_size = self._add_text_to_weave(
 
760
                            file_id, lines, heads, nostore_sha)
 
761
                        yield file_id, change[1][1], (entry.text_sha1, stat_value)
 
762
                    except errors.ExistingContent:
 
763
                        # No content change against a carry_over parent
 
764
                        # Perhaps this should also yield a fs hash update?
 
765
                        carried_over = True
 
766
                        entry.text_size = parent_entry.text_size
 
767
                        entry.text_sha1 = parent_entry.text_sha1
 
768
                elif kind == 'symlink':
 
769
                    # Wants a path hint?
 
770
                    entry.symlink_target = tree.get_symlink_target(file_id)
 
771
                    if (carry_over_possible and
 
772
                        parent_entry.symlink_target == entry.symlink_target):
 
773
                        carried_over = True
 
774
                    else:
 
775
                        self._add_text_to_weave(change[0], [], heads, None)
 
776
                elif kind == 'directory':
 
777
                    if carry_over_possible:
 
778
                        carried_over = True
 
779
                    else:
 
780
                        # Nothing to set on the entry.
 
781
                        # XXX: split into the Root and nonRoot versions.
 
782
                        if change[1][1] != '' or self.repository.supports_rich_root():
 
783
                            self._add_text_to_weave(change[0], [], heads, None)
 
784
                elif kind == 'tree-reference':
 
785
                    if not self.repository._format.supports_tree_reference:
 
786
                        # This isn't quite sane as an error, but we shouldn't
 
787
                        # ever see this code path in practice: tree's don't
 
788
                        # permit references when the repo doesn't support tree
 
789
                        # references.
 
790
                        raise errors.UnsupportedOperation(tree.add_reference,
 
791
                            self.repository)
 
792
                    entry.reference_revision = \
 
793
                        tree.get_reference_revision(change[0])
 
794
                    if (carry_over_possible and
 
795
                        parent_entry.reference_revision == reference_revision):
 
796
                        carried_over = True
 
797
                    else:
 
798
                        self._add_text_to_weave(change[0], [], heads, None)
 
799
                else:
 
800
                    raise AssertionError('unknown kind %r' % kind)
 
801
                if not carried_over:
 
802
                    entry.revision = modified_rev
 
803
                else:
 
804
                    entry.revision = parent_entry.revision
 
805
            else:
 
806
                entry = None
 
807
            new_path = change[1][1]
 
808
            inv_delta.append((change[1][0], new_path, change[0], entry))
 
809
            if new_path == '':
 
810
                seen_root = True
 
811
        self.new_inventory = None
 
812
        if len(inv_delta):
 
813
            self._any_changes = True
 
814
        if not seen_root:
 
815
            # housekeeping root entry changes do not affect no-change commits.
 
816
            self._require_root_change(tree)
 
817
        self.basis_delta_revision = basis_revision_id
 
818
 
502
819
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
503
820
        # Note: as we read the content directly from the tree, we know its not
504
821
        # been turned into unicode or badly split - but a broken tree
514
831
 
515
832
class RootCommitBuilder(CommitBuilder):
516
833
    """This commitbuilder actually records the root id"""
517
 
    
 
834
 
518
835
    # the root entry gets versioned properly by this builder.
519
836
    _versioned_root = True
520
837
 
527
844
        :param tree: The tree that is being committed.
528
845
        """
529
846
 
 
847
    def _require_root_change(self, tree):
 
848
        """Enforce an appropriate root object change.
 
849
 
 
850
        This is called once when record_iter_changes is called, if and only if
 
851
        the root was not in the delta calculated by record_iter_changes.
 
852
 
 
853
        :param tree: The tree which is being committed.
 
854
        """
 
855
        # versioned roots do not change unless the tree found a change.
 
856
 
530
857
 
531
858
######################################################################
532
859
# Repositories
613
940
 
614
941
    def _abort_write_group(self):
615
942
        """Template method for per-repository write group cleanup.
616
 
        
617
 
        This is called during abort before the write group is considered to be 
 
943
 
 
944
        This is called during abort before the write group is considered to be
618
945
        finished and should cleanup any internal state accrued during the write
619
946
        group. There is no requirement that data handed to the repository be
620
947
        *not* made available - this is not a rollback - but neither should any
626
953
 
627
954
    def add_fallback_repository(self, repository):
628
955
        """Add a repository to use for looking up data not held locally.
629
 
        
 
956
 
630
957
        :param repository: A repository.
631
958
        """
632
959
        if not self._format.supports_external_lookups:
637
964
        self.inventories.add_fallback_versioned_files(repository.inventories)
638
965
        self.revisions.add_fallback_versioned_files(repository.revisions)
639
966
        self.signatures.add_fallback_versioned_files(repository.signatures)
640
 
        self._fetch_order = 'topological'
641
967
 
642
968
    def _check_fallback_repository(self, repository):
643
969
        """Check that this repository can fallback to repository safely.
644
970
 
645
971
        Raise an error if not.
646
 
        
 
972
 
647
973
        :param repository: A repository to fallback to.
648
974
        """
649
975
        return InterRepository._assert_same_model(self, repository)
650
976
 
651
977
    def add_inventory(self, revision_id, inv, parents):
652
978
        """Add the inventory inv to the repository as revision_id.
653
 
        
 
979
 
654
980
        :param parents: The revision ids of the parents that revision_id
655
981
                        is known to have and are in the repository already.
656
982
 
758
1084
        self.revisions.add_lines(key, parents, osutils.split_lines(text))
759
1085
 
760
1086
    def all_revision_ids(self):
761
 
        """Returns a list of all the revision ids in the repository. 
 
1087
        """Returns a list of all the revision ids in the repository.
762
1088
 
763
1089
        This is conceptually deprecated because code should generally work on
764
1090
        the graph reachable from a particular revision, and ignore any other
770
1096
        return self._all_revision_ids()
771
1097
 
772
1098
    def _all_revision_ids(self):
773
 
        """Returns a list of all the revision ids in the repository. 
 
1099
        """Returns a list of all the revision ids in the repository.
774
1100
 
775
 
        These are in as much topological order as the underlying store can 
 
1101
        These are in as much topological order as the underlying store can
776
1102
        present.
777
1103
        """
778
1104
        raise NotImplementedError(self._all_revision_ids)
823
1149
        self._reconcile_does_inventory_gc = True
824
1150
        self._reconcile_fixes_text_parents = False
825
1151
        self._reconcile_backsup_inventory = True
826
 
        # not right yet - should be more semantically clear ? 
827
 
        # 
 
1152
        # not right yet - should be more semantically clear ?
 
1153
        #
828
1154
        # TODO: make sure to construct the right store classes, etc, depending
829
1155
        # on whether escaping is required.
830
1156
        self._warn_if_deprecated()
831
1157
        self._write_group = None
832
1158
        # Additional places to query for data.
833
1159
        self._fallback_repositories = []
834
 
        # What order should fetch operations request streams in?
835
 
        # The default is unordered as that is the cheapest for an origin to
836
 
        # provide.
837
 
        self._fetch_order = 'unordered'
838
 
        # Does this repository use deltas that can be fetched as-deltas ?
839
 
        # (E.g. knits, where the knit deltas can be transplanted intact.
840
 
        # We default to False, which will ensure that enough data to get
841
 
        # a full text out of any fetch stream will be grabbed.
842
 
        self._fetch_uses_deltas = False
843
 
        # Should fetch trigger a reconcile after the fetch? Only needed for
844
 
        # some repository formats that can suffer internal inconsistencies.
845
 
        self._fetch_reconcile = False
846
1160
        # An InventoryEntry cache, used during deserialization
847
1161
        self._inventory_entry_cache = fifo_cache.FIFOCache(10*1024)
848
1162
 
881
1195
        This causes caching within the repository obejct to start accumlating
882
1196
        data during reads, and allows a 'write_group' to be obtained. Write
883
1197
        groups must be used for actual data insertion.
884
 
        
 
1198
 
885
1199
        :param token: if this is already locked, then lock_write will fail
886
1200
            unless the token matches the existing lock.
887
1201
        :returns: a token if this instance supports tokens, otherwise None.
897
1211
 
898
1212
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
899
1213
        """
 
1214
        locked = self.is_locked()
900
1215
        result = self.control_files.lock_write(token=token)
901
1216
        for repo in self._fallback_repositories:
902
1217
            # Writes don't affect fallback repos
903
1218
            repo.lock_read()
904
 
        self._refresh_data()
 
1219
        if not locked:
 
1220
            self._refresh_data()
905
1221
        return result
906
1222
 
907
1223
    def lock_read(self):
 
1224
        locked = self.is_locked()
908
1225
        self.control_files.lock_read()
909
1226
        for repo in self._fallback_repositories:
910
1227
            repo.lock_read()
911
 
        self._refresh_data()
 
1228
        if not locked:
 
1229
            self._refresh_data()
912
1230
 
913
1231
    def get_physical_lock_status(self):
914
1232
        return self.control_files.get_physical_lock_status()
916
1234
    def leave_lock_in_place(self):
917
1235
        """Tell this repository not to release the physical lock when this
918
1236
        object is unlocked.
919
 
        
 
1237
 
920
1238
        If lock_write doesn't return a token, then this method is not supported.
921
1239
        """
922
1240
        self.control_files.leave_in_place()
1028
1346
    @needs_read_lock
1029
1347
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1030
1348
        """Return the revision ids that other has that this does not.
1031
 
        
 
1349
 
1032
1350
        These are returned in topological order.
1033
1351
 
1034
1352
        revision_id: only return revision ids included by revision_id.
1036
1354
        return InterRepository.get(other, self).search_missing_revision_ids(
1037
1355
            revision_id, find_ghosts)
1038
1356
 
1039
 
    @deprecated_method(one_two)
1040
 
    @needs_read_lock
1041
 
    def missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1042
 
        """Return the revision ids that other has that this does not.
1043
 
        
1044
 
        These are returned in topological order.
1045
 
 
1046
 
        revision_id: only return revision ids included by revision_id.
1047
 
        """
1048
 
        keys =  self.search_missing_revision_ids(
1049
 
            other, revision_id, find_ghosts).get_keys()
1050
 
        other.lock_read()
1051
 
        try:
1052
 
            parents = other.get_graph().get_parent_map(keys)
1053
 
        finally:
1054
 
            other.unlock()
1055
 
        return tsort.topo_sort(parents)
1056
 
 
1057
1357
    @staticmethod
1058
1358
    def open(base):
1059
1359
        """Open the repository rooted at base.
1066
1366
 
1067
1367
    def copy_content_into(self, destination, revision_id=None):
1068
1368
        """Make a complete copy of the content in self into destination.
1069
 
        
1070
 
        This is a destructive operation! Do not use it on existing 
 
1369
 
 
1370
        This is a destructive operation! Do not use it on existing
1071
1371
        repositories.
1072
1372
        """
1073
1373
        return InterRepository.get(self, destination).copy_content(revision_id)
1087
1387
 
1088
1388
    def _commit_write_group(self):
1089
1389
        """Template method for per-repository write group cleanup.
1090
 
        
1091
 
        This is called before the write group is considered to be 
 
1390
 
 
1391
        This is called before the write group is considered to be
1092
1392
        finished and should ensure that all data handed to the repository
1093
 
        for writing during the write group is safely committed (to the 
 
1393
        for writing during the write group is safely committed (to the
1094
1394
        extent possible considering file system caching etc).
1095
1395
        """
1096
1396
 
1097
1397
    def suspend_write_group(self):
1098
1398
        raise errors.UnsuspendableWriteGroup(self)
1099
1399
 
 
1400
    def refresh_data(self):
 
1401
        """Re-read any data needed to to synchronise with disk.
 
1402
 
 
1403
        This method is intended to be called after another repository instance
 
1404
        (such as one used by a smart server) has inserted data into the
 
1405
        repository. It may not be called during a write group, but may be
 
1406
        called at any other time.
 
1407
        """
 
1408
        if self.is_in_write_group():
 
1409
            raise errors.InternalBzrError(
 
1410
                "May not refresh_data while in a write group.")
 
1411
        self._refresh_data()
 
1412
 
1100
1413
    def resume_write_group(self, tokens):
1101
1414
        if not self.is_write_locked():
1102
1415
            raise errors.NotWriteLocked(self)
1105
1418
        self._resume_write_group(tokens)
1106
1419
        # so we can detect unlock/relock - the write group is now entered.
1107
1420
        self._write_group = self.get_transaction()
1108
 
    
 
1421
 
1109
1422
    def _resume_write_group(self, tokens):
1110
1423
        raise errors.UnsuspendableWriteGroup(self)
1111
1424
 
1112
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
 
1425
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
1426
            fetch_spec=None):
1113
1427
        """Fetch the content required to construct revision_id from source.
1114
1428
 
1115
 
        If revision_id is None all content is copied.
 
1429
        If revision_id is None and fetch_spec is None, then all content is
 
1430
        copied.
 
1431
 
 
1432
        fetch() may not be used when the repository is in a write group -
 
1433
        either finish the current write group before using fetch, or use
 
1434
        fetch before starting the write group.
 
1435
 
1116
1436
        :param find_ghosts: Find and copy revisions in the source that are
1117
1437
            ghosts in the target (and not reachable directly by walking out to
1118
1438
            the first-present revision in target from revision_id).
 
1439
        :param revision_id: If specified, all the content needed for this
 
1440
            revision ID will be copied to the target.  Fetch will determine for
 
1441
            itself which content needs to be copied.
 
1442
        :param fetch_spec: If specified, a SearchResult or
 
1443
            PendingAncestryResult that describes which revisions to copy.  This
 
1444
            allows copying multiple heads at once.  Mutually exclusive with
 
1445
            revision_id.
1119
1446
        """
 
1447
        if fetch_spec is not None and revision_id is not None:
 
1448
            raise AssertionError(
 
1449
                "fetch_spec and revision_id are mutually exclusive.")
 
1450
        if self.is_in_write_group():
 
1451
            raise errors.InternalBzrError(
 
1452
                "May not fetch while in a write group.")
1120
1453
        # fast path same-url fetch operations
1121
 
        if self.has_same_location(source):
 
1454
        if self.has_same_location(source) and fetch_spec is None:
1122
1455
            # check that last_revision is in 'from' and then return a
1123
1456
            # no-operation.
1124
1457
            if (revision_id is not None and
1130
1463
        # IncompatibleRepositories when asked to fetch.
1131
1464
        inter = InterRepository.get(source, self)
1132
1465
        return inter.fetch(revision_id=revision_id, pb=pb,
1133
 
            find_ghosts=find_ghosts)
 
1466
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1134
1467
 
1135
1468
    def create_bundle(self, target, base, fileobj, format=None):
1136
1469
        return serializer.write_bundle(self, target, base, fileobj, format)
1139
1472
                           timezone=None, committer=None, revprops=None,
1140
1473
                           revision_id=None):
1141
1474
        """Obtain a CommitBuilder for this repository.
1142
 
        
 
1475
 
1143
1476
        :param branch: Branch to commit to.
1144
1477
        :param parents: Revision ids of the parents of the new revision.
1145
1478
        :param config: Configuration to use.
1207
1540
 
1208
1541
    def _start_write_group(self):
1209
1542
        """Template method for per-repository write group startup.
1210
 
        
1211
 
        This is called before the write group is considered to be 
 
1543
 
 
1544
        This is called before the write group is considered to be
1212
1545
        entered.
1213
1546
        """
1214
1547
 
1239
1572
        """Return a sink for streaming into this repository."""
1240
1573
        return StreamSink(self)
1241
1574
 
 
1575
    def _get_source(self, to_format):
 
1576
        """Return a source for streaming from this repository."""
 
1577
        return StreamSource(self, to_format)
 
1578
 
1242
1579
    @needs_read_lock
1243
1580
    def has_revision(self, revision_id):
1244
1581
        """True if this repository has a copy of the revision."""
1267
1604
    @needs_read_lock
1268
1605
    def get_revision_reconcile(self, revision_id):
1269
1606
        """'reconcile' helper routine that allows access to a revision always.
1270
 
        
 
1607
 
1271
1608
        This variant of get_revision does not cross check the weave graph
1272
1609
        against the revision one as get_revision does: but it should only
1273
1610
        be used by reconcile, or reconcile-alike commands that are correcting
1302
1639
        # TODO: jam 20070210 This shouldn't be necessary since get_revision
1303
1640
        #       would have already do it.
1304
1641
        # TODO: jam 20070210 Just use _serializer.write_revision_to_string()
 
1642
        # TODO: this can't just be replaced by:
 
1643
        # return self._serializer.write_revision_to_string(
 
1644
        #     self.get_revision(revision_id))
 
1645
        # as cStringIO preservers the encoding unlike write_revision_to_string
 
1646
        # or some other call down the path.
1305
1647
        rev = self.get_revision(revision_id)
1306
1648
        rev_tmp = cStringIO.StringIO()
1307
1649
        # the current serializer..
1309
1651
        rev_tmp.seek(0)
1310
1652
        return rev_tmp.getvalue()
1311
1653
 
1312
 
    def get_deltas_for_revisions(self, revisions):
 
1654
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1313
1655
        """Produce a generator of revision deltas.
1314
 
        
 
1656
 
1315
1657
        Note that the input is a sequence of REVISIONS, not revision_ids.
1316
1658
        Trees will be held in memory until the generator exits.
1317
1659
        Each delta is relative to the revision's lefthand predecessor.
 
1660
 
 
1661
        :param specific_fileids: if not None, the result is filtered
 
1662
          so that only those file-ids, their parents and their
 
1663
          children are included.
1318
1664
        """
 
1665
        # Get the revision-ids of interest
1319
1666
        required_trees = set()
1320
1667
        for revision in revisions:
1321
1668
            required_trees.add(revision.revision_id)
1322
1669
            required_trees.update(revision.parent_ids[:1])
1323
 
        trees = dict((t.get_revision_id(), t) for 
1324
 
                     t in self.revision_trees(required_trees))
 
1670
 
 
1671
        # Get the matching filtered trees. Note that it's more
 
1672
        # efficient to pass filtered trees to changes_from() rather
 
1673
        # than doing the filtering afterwards. changes_from() could
 
1674
        # arguably do the filtering itself but it's path-based, not
 
1675
        # file-id based, so filtering before or afterwards is
 
1676
        # currently easier.
 
1677
        if specific_fileids is None:
 
1678
            trees = dict((t.get_revision_id(), t) for
 
1679
                t in self.revision_trees(required_trees))
 
1680
        else:
 
1681
            trees = dict((t.get_revision_id(), t) for
 
1682
                t in self._filtered_revision_trees(required_trees,
 
1683
                specific_fileids))
 
1684
 
 
1685
        # Calculate the deltas
1325
1686
        for revision in revisions:
1326
1687
            if not revision.parent_ids:
1327
1688
                old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
1330
1691
            yield trees[revision.revision_id].changes_from(old_tree)
1331
1692
 
1332
1693
    @needs_read_lock
1333
 
    def get_revision_delta(self, revision_id):
 
1694
    def get_revision_delta(self, revision_id, specific_fileids=None):
1334
1695
        """Return the delta for one revision.
1335
1696
 
1336
1697
        The delta is relative to the left-hand predecessor of the
1337
1698
        revision.
 
1699
 
 
1700
        :param specific_fileids: if not None, the result is filtered
 
1701
          so that only those file-ids, their parents and their
 
1702
          children are included.
1338
1703
        """
1339
1704
        r = self.get_revision(revision_id)
1340
 
        return list(self.get_deltas_for_revisions([r]))[0]
 
1705
        return list(self.get_deltas_for_revisions([r],
 
1706
            specific_fileids=specific_fileids))[0]
1341
1707
 
1342
1708
    @needs_write_lock
1343
1709
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1352
1718
    def find_text_key_references(self):
1353
1719
        """Find the text key references within the repository.
1354
1720
 
1355
 
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
1356
 
        revision_ids. Each altered file-ids has the exact revision_ids that
1357
 
        altered it listed explicitly.
1358
1721
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1359
1722
            to whether they were referred to by the inventory of the
1360
1723
            revision_id that they contain. The inventory texts from all present
1391
1754
 
1392
1755
        # this code needs to read every new line in every inventory for the
1393
1756
        # inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
1394
 
        # not present in one of those inventories is unnecessary but not 
 
1757
        # not present in one of those inventories is unnecessary but not
1395
1758
        # harmful because we are filtering by the revision id marker in the
1396
 
        # inventory lines : we only select file ids altered in one of those  
 
1759
        # inventory lines : we only select file ids altered in one of those
1397
1760
        # revisions. We don't need to see all lines in the inventory because
1398
1761
        # only those added in an inventory in rev X can contain a revision=X
1399
1762
        # line.
1449
1812
                result[key] = True
1450
1813
        return result
1451
1814
 
 
1815
    def _inventory_xml_lines_for_keys(self, keys):
 
1816
        """Get a line iterator of the sort needed for findind references.
 
1817
 
 
1818
        Not relevant for non-xml inventory repositories.
 
1819
 
 
1820
        Ghosts in revision_keys are ignored.
 
1821
 
 
1822
        :param revision_keys: The revision keys for the inventories to inspect.
 
1823
        :return: An iterator over (inventory line, revid) for the fulltexts of
 
1824
            all of the xml inventories specified by revision_keys.
 
1825
        """
 
1826
        stream = self.inventories.get_record_stream(keys, 'unordered', True)
 
1827
        for record in stream:
 
1828
            if record.storage_kind != 'absent':
 
1829
                chunks = record.get_bytes_as('chunked')
 
1830
                revid = record.key[-1]
 
1831
                lines = osutils.chunks_to_lines(chunks)
 
1832
                for line in lines:
 
1833
                    yield line, revid
 
1834
 
1452
1835
    def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
1453
1836
        revision_ids):
1454
1837
        """Helper routine for fileids_altered_by_revision_ids.
1464
1847
        revision_ids. Each altered file-ids has the exact revision_ids that
1465
1848
        altered it listed explicitly.
1466
1849
        """
 
1850
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
1851
                line_iterator).iterkeys())
 
1852
        # Note that revision_ids are revision keys.
 
1853
        parent_maps = self.revisions.get_parent_map(revision_ids)
 
1854
        parents = set()
 
1855
        map(parents.update, parent_maps.itervalues())
 
1856
        parents.difference_update(revision_ids)
 
1857
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
1858
            self._inventory_xml_lines_for_keys(parents)))
 
1859
        new_keys = seen - parent_seen
1467
1860
        result = {}
1468
1861
        setdefault = result.setdefault
1469
 
        for key in \
1470
 
            self._find_text_key_references_from_xml_inventory_lines(
1471
 
                line_iterator).iterkeys():
1472
 
            # once data is all ensured-consistent; then this is
1473
 
            # if revision_id == version_id
1474
 
            if key[-1:] in revision_ids:
1475
 
                setdefault(key[0], set()).add(key[-1])
 
1862
        for key in new_keys:
 
1863
            setdefault(key[0], set()).add(key[-1])
1476
1864
        return result
1477
1865
 
1478
1866
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
1521
1909
        for record in self.texts.get_record_stream(text_keys, 'unordered', True):
1522
1910
            if record.storage_kind == 'absent':
1523
1911
                raise errors.RevisionNotPresent(record.key, self)
1524
 
            yield text_keys[record.key], record.get_bytes_as('fulltext')
 
1912
            yield text_keys[record.key], record.get_bytes_as('chunked')
1525
1913
 
1526
1914
    def _generate_text_key_index(self, text_key_references=None,
1527
1915
        ancestors=None):
1576
1964
        batch_size = 10 # should be ~150MB on a 55K path tree
1577
1965
        batch_count = len(revision_order) / batch_size + 1
1578
1966
        processed_texts = 0
1579
 
        pb.update("Calculating text parents.", processed_texts, text_count)
 
1967
        pb.update("Calculating text parents", processed_texts, text_count)
1580
1968
        for offset in xrange(batch_count):
1581
1969
            to_query = revision_order[offset * batch_size:(offset + 1) *
1582
1970
                batch_size]
1586
1974
                revision_id = rev_tree.get_revision_id()
1587
1975
                parent_ids = ancestors[revision_id]
1588
1976
                for text_key in revision_keys[revision_id]:
1589
 
                    pb.update("Calculating text parents.", processed_texts)
 
1977
                    pb.update("Calculating text parents", processed_texts)
1590
1978
                    processed_texts += 1
1591
1979
                    candidate_parents = []
1592
1980
                    for parent_id in parent_ids:
1688
2076
        inventories in memory, but will only parse a single inventory at a
1689
2077
        time.
1690
2078
 
 
2079
        :param revision_ids: The expected revision ids of the inventories.
1691
2080
        :return: An iterator of inventories.
1692
2081
        """
1693
2082
        if ((None in revision_ids)
1714
2103
            yield ''.join(chunks), key[-1]
1715
2104
 
1716
2105
    def deserialise_inventory(self, revision_id, xml):
1717
 
        """Transform the xml into an inventory object. 
 
2106
        """Transform the xml into an inventory object.
1718
2107
 
1719
2108
        :param revision_id: The expected revision id of the inventory.
1720
2109
        :param xml: A serialised inventory.
1809
2198
        for repositories to maintain loaded indices across multiple locks
1810
2199
        by checking inside their implementation of this method to see
1811
2200
        whether their indices are still valid. This depends of course on
1812
 
        the disk format being validatable in this manner.
 
2201
        the disk format being validatable in this manner. This method is
 
2202
        also called by the refresh_data() public interface to cause a refresh
 
2203
        to occur while in a write lock so that data inserted by a smart server
 
2204
        push operation is visible on the client's instance of the physical
 
2205
        repository.
1813
2206
        """
1814
2207
 
1815
2208
    @needs_read_lock
1822
2215
        # TODO: refactor this to use an existing revision object
1823
2216
        # so we don't need to read it in twice.
1824
2217
        if revision_id == _mod_revision.NULL_REVISION:
1825
 
            return RevisionTree(self, Inventory(root_id=None), 
 
2218
            return RevisionTree(self, Inventory(root_id=None),
1826
2219
                                _mod_revision.NULL_REVISION)
1827
2220
        else:
1828
2221
            inv = self.get_revision_inventory(revision_id)
1829
2222
            return RevisionTree(self, inv, revision_id)
1830
2223
 
1831
2224
    def revision_trees(self, revision_ids):
1832
 
        """Return Tree for a revision on this branch.
 
2225
        """Return Trees for revisions in this repository.
1833
2226
 
1834
 
        `revision_id` may not be None or 'null:'"""
 
2227
        :param revision_ids: a sequence of revision-ids;
 
2228
          a revision-id may not be None or 'null:'
 
2229
        """
1835
2230
        inventories = self.iter_inventories(revision_ids)
1836
2231
        for inv in inventories:
1837
2232
            yield RevisionTree(self, inv, inv.revision_id)
1838
2233
 
 
2234
    def _filtered_revision_trees(self, revision_ids, file_ids):
 
2235
        """Return Tree for a revision on this branch with only some files.
 
2236
 
 
2237
        :param revision_ids: a sequence of revision-ids;
 
2238
          a revision-id may not be None or 'null:'
 
2239
        :param file_ids: if not None, the result is filtered
 
2240
          so that only those file-ids, their parents and their
 
2241
          children are included.
 
2242
        """
 
2243
        inventories = self.iter_inventories(revision_ids)
 
2244
        for inv in inventories:
 
2245
            # Should we introduce a FilteredRevisionTree class rather
 
2246
            # than pre-filter the inventory here?
 
2247
            filtered_inv = inv.filter(file_ids)
 
2248
            yield RevisionTree(self, filtered_inv, filtered_inv.revision_id)
 
2249
 
1839
2250
    @needs_read_lock
1840
2251
    def get_ancestry(self, revision_id, topo_sorted=True):
1841
2252
        """Return a list of revision-ids integrated by a revision.
1842
2253
 
1843
 
        The first element of the list is always None, indicating the origin 
1844
 
        revision.  This might change when we have history horizons, or 
 
2254
        The first element of the list is always None, indicating the origin
 
2255
        revision.  This might change when we have history horizons, or
1845
2256
        perhaps we should have a new API.
1846
 
        
 
2257
 
1847
2258
        This is topologically sorted.
1848
2259
        """
1849
2260
        if _mod_revision.is_null(revision_id):
1873
2284
        types it should be a no-op that just returns.
1874
2285
 
1875
2286
        This stub method does not require a lock, but subclasses should use
1876
 
        @needs_write_lock as this is a long running call its reasonable to 
 
2287
        @needs_write_lock as this is a long running call its reasonable to
1877
2288
        implicitly lock for the user.
1878
2289
        """
1879
2290
 
1880
 
    @needs_read_lock
1881
 
    @deprecated_method(one_six)
1882
 
    def print_file(self, file, revision_id):
1883
 
        """Print `file` to stdout.
1884
 
        
1885
 
        FIXME RBC 20060125 as John Meinel points out this is a bad api
1886
 
        - it writes to stdout, it assumes that that is valid etc. Fix
1887
 
        by creating a new more flexible convenience function.
1888
 
        """
1889
 
        tree = self.revision_tree(revision_id)
1890
 
        # use inventory as it was in that revision
1891
 
        file_id = tree.inventory.path2id(file)
1892
 
        if not file_id:
1893
 
            # TODO: jam 20060427 Write a test for this code path
1894
 
            #       it had a bug in it, and was raising the wrong
1895
 
            #       exception.
1896
 
            raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
1897
 
        tree.print_file(file_id)
1898
 
 
1899
2291
    def get_transaction(self):
1900
2292
        return self.control_files.get_transaction()
1901
2293
 
1902
 
    @deprecated_method(one_one)
1903
 
    def get_parents(self, revision_ids):
1904
 
        """See StackedParentsProvider.get_parents"""
1905
 
        parent_map = self.get_parent_map(revision_ids)
1906
 
        return [parent_map.get(r, None) for r in revision_ids]
1907
 
 
1908
2294
    def get_parent_map(self, revision_ids):
1909
2295
        """See graph._StackedParentsProvider.get_parent_map"""
1910
2296
        # revisions index works in keys; this just works in revisions
1939
2325
                [parents_provider, other_repository._make_parents_provider()])
1940
2326
        return graph.Graph(parents_provider)
1941
2327
 
1942
 
    def _get_versioned_file_checker(self):
1943
 
        """Return an object suitable for checking versioned files."""
1944
 
        return _VersionedFileChecker(self)
 
2328
    def _get_versioned_file_checker(self, text_key_references=None):
 
2329
        """Return an object suitable for checking versioned files.
 
2330
        
 
2331
        :param text_key_references: if non-None, an already built
 
2332
            dictionary mapping text keys ((fileid, revision_id) tuples)
 
2333
            to whether they were referred to by the inventory of the
 
2334
            revision_id that they contain. If None, this will be
 
2335
            calculated.
 
2336
        """
 
2337
        return _VersionedFileChecker(self,
 
2338
            text_key_references=text_key_references)
1945
2339
 
1946
2340
    def revision_ids_to_search_result(self, result_set):
1947
2341
        """Convert a set of revision ids to a graph SearchResult."""
1967
2361
                          working trees.
1968
2362
        """
1969
2363
        raise NotImplementedError(self.set_make_working_trees)
1970
 
    
 
2364
 
1971
2365
    def make_working_trees(self):
1972
2366
        """Returns the policy for making working trees on new branches."""
1973
2367
        raise NotImplementedError(self.make_working_trees)
2038
2432
                    revision_id.decode('ascii')
2039
2433
                except UnicodeDecodeError:
2040
2434
                    raise errors.NonAsciiRevisionId(method, self)
2041
 
    
 
2435
 
2042
2436
    def revision_graph_can_have_wrong_parents(self):
2043
2437
        """Is it possible for this repository to have a revision graph with
2044
2438
        incorrect parents?
2162
2556
 
2163
2557
class MetaDirRepository(Repository):
2164
2558
    """Repositories in the new meta-dir layout.
2165
 
    
 
2559
 
2166
2560
    :ivar _transport: Transport for access to repository control files,
2167
2561
        typically pointing to .bzr/repository.
2168
2562
    """
2193
2587
        else:
2194
2588
            self._transport.put_bytes('no-working-trees', '',
2195
2589
                mode=self.bzrdir._get_file_mode())
2196
 
    
 
2590
 
2197
2591
    def make_working_trees(self):
2198
2592
        """Returns the policy for making working trees on new branches."""
2199
2593
        return not self._transport.has('no-working-trees')
2207
2601
            control_files)
2208
2602
 
2209
2603
 
2210
 
class RepositoryFormatRegistry(registry.Registry):
2211
 
    """Registry of RepositoryFormats."""
2212
 
 
2213
 
    def __init__(self, other_registry=None):
2214
 
        registry.Registry.__init__(self)
2215
 
        self._other_registry = other_registry
2216
 
 
2217
 
    def register_lazy(self, key, module_name, member_name,
2218
 
                      help=None, info=None,
2219
 
                      override_existing=False):
2220
 
        # Overridden to allow capturing registrations to two separate
2221
 
        # registries in a single call.
2222
 
        registry.Registry.register_lazy(self, key, module_name, member_name,
2223
 
                help=help, info=info, override_existing=override_existing)
2224
 
        if self._other_registry is not None:
2225
 
            self._other_registry.register_lazy(key, module_name, member_name,
2226
 
                help=help, info=info, override_existing=override_existing)
2227
 
 
2228
 
    def get(self, format_string):
2229
 
        r = registry.Registry.get(self, format_string)
2230
 
        if callable(r):
2231
 
            r = r()
2232
 
        return r
2233
 
    
2234
 
 
2235
 
network_format_registry = RepositoryFormatRegistry()
 
2604
network_format_registry = registry.FormatRegistry()
2236
2605
"""Registry of formats indexed by their network name.
2237
2606
 
2238
2607
The network name for a repository format is an identifier that can be used when
2241
2610
"""
2242
2611
 
2243
2612
 
2244
 
format_registry = RepositoryFormatRegistry(network_format_registry)
 
2613
format_registry = registry.FormatRegistry(network_format_registry)
2245
2614
"""Registry of formats, indexed by their BzrDirMetaFormat format string.
2246
2615
 
2247
2616
This can contain either format instances themselves, or classes/factories that
2273
2642
    consistency.
2274
2643
 
2275
2644
    Once a format is deprecated, just deprecate the initialize and open
2276
 
    methods on the format class. Do not deprecate the object, as the 
 
2645
    methods on the format class. Do not deprecate the object, as the
2277
2646
    object may be created even when a repository instance hasn't been
2278
2647
    created.
2279
2648
 
2290
2659
    # Can this repository be given external locations to lookup additional
2291
2660
    # data. Set to True or False in derived classes.
2292
2661
    supports_external_lookups = None
 
2662
    # What order should fetch operations request streams in?
 
2663
    # The default is unordered as that is the cheapest for an origin to
 
2664
    # provide.
 
2665
    _fetch_order = 'unordered'
 
2666
    # Does this repository format use deltas that can be fetched as-deltas ?
 
2667
    # (E.g. knits, where the knit deltas can be transplanted intact.
 
2668
    # We default to False, which will ensure that enough data to get
 
2669
    # a full text out of any fetch stream will be grabbed.
 
2670
    _fetch_uses_deltas = False
 
2671
    # Should fetch trigger a reconcile after the fetch? Only needed for
 
2672
    # some repository formats that can suffer internal inconsistencies.
 
2673
    _fetch_reconcile = False
 
2674
    # Does this format have < O(tree_size) delta generation. Used to hint what
 
2675
    # code path for commit, amongst other things.
 
2676
    fast_deltas = None
2293
2677
 
2294
2678
    def __str__(self):
2295
2679
        return "<%s>" % self.__class__.__name__
2304
2688
    @classmethod
2305
2689
    def find_format(klass, a_bzrdir):
2306
2690
        """Return the format for the repository object in a_bzrdir.
2307
 
        
 
2691
 
2308
2692
        This is used by bzr native formats that have a "format" file in
2309
 
        the repository.  Other methods may be used by different types of 
 
2693
        the repository.  Other methods may be used by different types of
2310
2694
        control directory.
2311
2695
        """
2312
2696
        try:
2326
2710
    @classmethod
2327
2711
    def unregister_format(klass, format):
2328
2712
        format_registry.remove(format.get_format_string())
2329
 
    
 
2713
 
2330
2714
    @classmethod
2331
2715
    def get_default_format(klass):
2332
2716
        """Return the current default format."""
2335
2719
 
2336
2720
    def get_format_string(self):
2337
2721
        """Return the ASCII format string that identifies this format.
2338
 
        
2339
 
        Note that in pre format ?? repositories the format string is 
 
2722
 
 
2723
        Note that in pre format ?? repositories the format string is
2340
2724
        not permitted nor written to disk.
2341
2725
        """
2342
2726
        raise NotImplementedError(self.get_format_string)
2373
2757
        :param a_bzrdir: The bzrdir to put the new repository in it.
2374
2758
        :param shared: The repository should be initialized as a sharable one.
2375
2759
        :returns: The new repository object.
2376
 
        
 
2760
 
2377
2761
        This may raise UninitializableFormat if shared repository are not
2378
2762
        compatible the a_bzrdir.
2379
2763
        """
2383
2767
        """Is this format supported?
2384
2768
 
2385
2769
        Supported formats must be initializable and openable.
2386
 
        Unsupported formats may not support initialization or committing or 
 
2770
        Unsupported formats may not support initialization or committing or
2387
2771
        some other features depending on the reason for not being supported.
2388
2772
        """
2389
2773
        return True
2403
2787
 
2404
2788
    def open(self, a_bzrdir, _found=False):
2405
2789
        """Return an instance of this format for the bzrdir a_bzrdir.
2406
 
        
 
2790
 
2407
2791
        _found is a private parameter, do not use it.
2408
2792
        """
2409
2793
        raise NotImplementedError(self.open)
2474
2858
)
2475
2859
 
2476
2860
# formats which have no format string are not discoverable or independently
2477
 
# creatable on disk, so are not registered in format_registry.  They're 
 
2861
# creatable on disk, so are not registered in format_registry.  They're
2478
2862
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
2479
2863
# needed, it's constructed directly by the BzrDir.  Non-native formats where
2480
2864
# the repository is not separately opened are similar.
2547
2931
    'RepositoryFormatKnitPack6RichRoot',
2548
2932
    )
2549
2933
 
2550
 
# Development formats. 
 
2934
# Development formats.
2551
2935
# 1.7->1.8 go below here
2552
2936
format_registry.register_lazy(
2553
2937
    "Bazaar development format 2 (needs bzr.dev from before 1.8)\n",
2566
2950
    """This class represents operations taking place between two repositories.
2567
2951
 
2568
2952
    Its instances have methods like copy_content and fetch, and contain
2569
 
    references to the source and target repositories these operations can be 
 
2953
    references to the source and target repositories these operations can be
2570
2954
    carried out on.
2571
2955
 
2572
2956
    Often we will provide convenience methods on 'repository' which carry out
2574
2958
    InterRepository.get(other).method_name(parameters).
2575
2959
    """
2576
2960
 
2577
 
    _walk_to_common_revisions_batch_size = 1
 
2961
    _walk_to_common_revisions_batch_size = 50
2578
2962
    _optimisers = []
2579
2963
    """The available optimised InterRepository types."""
2580
2964
 
2581
 
    def __init__(self, source, target):
2582
 
        InterObject.__init__(self, source, target)
2583
 
        # These two attributes may be overridden by e.g. InterOtherToRemote to
2584
 
        # provide a faster implementation.
2585
 
        self.target_get_graph = self.target.get_graph
2586
 
        self.target_get_parent_map = self.target.get_parent_map
2587
 
 
 
2965
    @needs_write_lock
2588
2966
    def copy_content(self, revision_id=None):
2589
 
        raise NotImplementedError(self.copy_content)
2590
 
 
2591
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2967
        """Make a complete copy of the content in self into destination.
 
2968
 
 
2969
        This is a destructive operation! Do not use it on existing
 
2970
        repositories.
 
2971
 
 
2972
        :param revision_id: Only copy the content needed to construct
 
2973
                            revision_id and its parents.
 
2974
        """
 
2975
        try:
 
2976
            self.target.set_make_working_trees(self.source.make_working_trees())
 
2977
        except NotImplementedError:
 
2978
            pass
 
2979
        self.target.fetch(self.source, revision_id=revision_id)
 
2980
 
 
2981
    @needs_write_lock
 
2982
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
2983
            fetch_spec=None):
2592
2984
        """Fetch the content required to construct revision_id.
2593
2985
 
2594
2986
        The content is copied from self.source to self.target.
2597
2989
                            content is copied.
2598
2990
        :param pb: optional progress bar to use for progress reports. If not
2599
2991
                   provided a default one will be created.
2600
 
 
2601
 
        :returns: (copied_revision_count, failures).
 
2992
        :return: None.
2602
2993
        """
2603
 
        # Normally we should find a specific InterRepository subclass to do
2604
 
        # the fetch; if nothing else then at least InterSameDataRepository.
2605
 
        # If none of them is suitable it looks like fetching is not possible;
2606
 
        # we try to give a good message why.  _assert_same_model will probably
2607
 
        # give a helpful message; otherwise a generic one.
2608
 
        self._assert_same_model(self.source, self.target)
2609
 
        raise errors.IncompatibleRepositories(self.source, self.target,
2610
 
            "no suitableInterRepository found")
 
2994
        from bzrlib.fetch import RepoFetcher
 
2995
        f = RepoFetcher(to_repository=self.target,
 
2996
                               from_repository=self.source,
 
2997
                               last_revision=revision_id,
 
2998
                               fetch_spec=fetch_spec,
 
2999
                               pb=pb, find_ghosts=find_ghosts)
2611
3000
 
2612
3001
    def _walk_to_common_revisions(self, revision_ids):
2613
3002
        """Walk out from revision_ids in source to revisions target has.
2615
3004
        :param revision_ids: The start point for the search.
2616
3005
        :return: A set of revision ids.
2617
3006
        """
2618
 
        target_graph = self.target_get_graph()
 
3007
        target_graph = self.target.get_graph()
2619
3008
        revision_ids = frozenset(revision_ids)
2620
3009
        # Fast path for the case where all the revisions are already in the
2621
3010
        # target repo.
2668
3057
                break
2669
3058
        return searcher.get_result()
2670
3059
 
2671
 
    @deprecated_method(one_two)
2672
 
    @needs_read_lock
2673
 
    def missing_revision_ids(self, revision_id=None, find_ghosts=True):
2674
 
        """Return the revision ids that source has that target does not.
2675
 
        
2676
 
        These are returned in topological order.
2677
 
 
2678
 
        :param revision_id: only return revision ids included by this
2679
 
                            revision_id.
2680
 
        :param find_ghosts: If True find missing revisions in deep history
2681
 
            rather than just finding the surface difference.
2682
 
        """
2683
 
        return list(self.search_missing_revision_ids(
2684
 
            revision_id, find_ghosts).get_keys())
2685
 
 
2686
3060
    @needs_read_lock
2687
3061
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2688
3062
        """Return the revision ids that source has that target does not.
2689
 
        
 
3063
 
2690
3064
        :param revision_id: only return revision ids included by this
2691
3065
                            revision_id.
2692
3066
        :param find_ghosts: If True find missing revisions in deep history
2711
3085
    @staticmethod
2712
3086
    def _same_model(source, target):
2713
3087
        """True if source and target have the same data representation.
2714
 
        
 
3088
 
2715
3089
        Note: this is always called on the base class; overriding it in a
2716
3090
        subclass will have no effect.
2717
3091
        """
2735
3109
 
2736
3110
class InterSameDataRepository(InterRepository):
2737
3111
    """Code for converting between repositories that represent the same data.
2738
 
    
 
3112
 
2739
3113
    Data format and model must match for this to work.
2740
3114
    """
2741
3115
 
2742
3116
    @classmethod
2743
3117
    def _get_repo_format_to_test(self):
2744
3118
        """Repository format for testing with.
2745
 
        
 
3119
 
2746
3120
        InterSameData can pull from subtree to subtree and from non-subtree to
2747
3121
        non-subtree, so we test this with the richest repository format.
2748
3122
        """
2753
3127
    def is_compatible(source, target):
2754
3128
        return InterRepository._same_model(source, target)
2755
3129
 
2756
 
    @needs_write_lock
2757
 
    def copy_content(self, revision_id=None):
2758
 
        """Make a complete copy of the content in self into destination.
2759
 
 
2760
 
        This copies both the repository's revision data, and configuration information
2761
 
        such as the make_working_trees setting.
2762
 
        
2763
 
        This is a destructive operation! Do not use it on existing 
2764
 
        repositories.
2765
 
 
2766
 
        :param revision_id: Only copy the content needed to construct
2767
 
                            revision_id and its parents.
2768
 
        """
2769
 
        try:
2770
 
            self.target.set_make_working_trees(self.source.make_working_trees())
2771
 
        except NotImplementedError:
2772
 
            pass
2773
 
        # but don't bother fetching if we have the needed data now.
2774
 
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
2775
 
            self.target.has_revision(revision_id)):
2776
 
            return
2777
 
        self.target.fetch(self.source, revision_id=revision_id)
2778
 
 
2779
 
    @needs_write_lock
2780
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2781
 
        """See InterRepository.fetch()."""
2782
 
        from bzrlib.fetch import RepoFetcher
2783
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2784
 
               self.source, self.source._format, self.target,
2785
 
               self.target._format)
2786
 
        f = RepoFetcher(to_repository=self.target,
2787
 
                               from_repository=self.source,
2788
 
                               last_revision=revision_id,
2789
 
                               pb=pb, find_ghosts=find_ghosts)
2790
 
        return f.count_copied, f.failed_revisions
2791
 
 
2792
3130
 
2793
3131
class InterWeaveRepo(InterSameDataRepository):
2794
3132
    """Optimised code paths between Weave based repositories.
2795
 
    
 
3133
 
2796
3134
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
2797
3135
    implemented lazy inter-object optimisation.
2798
3136
    """
2805
3143
    @staticmethod
2806
3144
    def is_compatible(source, target):
2807
3145
        """Be compatible with known Weave formats.
2808
 
        
 
3146
 
2809
3147
        We don't test for the stores being of specific types because that
2810
 
        could lead to confusing results, and there is no need to be 
 
3148
        could lead to confusing results, and there is no need to be
2811
3149
        overly general.
2812
3150
        """
2813
3151
        from bzrlib.repofmt.weaverepo import (
2824
3162
                                                RepositoryFormat7)))
2825
3163
        except AttributeError:
2826
3164
            return False
2827
 
    
 
3165
 
2828
3166
    @needs_write_lock
2829
3167
    def copy_content(self, revision_id=None):
2830
3168
        """See InterRepository.copy_content()."""
2857
3195
        else:
2858
3196
            self.target.fetch(self.source, revision_id=revision_id)
2859
3197
 
2860
 
    @needs_write_lock
2861
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2862
 
        """See InterRepository.fetch()."""
2863
 
        from bzrlib.fetch import RepoFetcher
2864
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2865
 
               self.source, self.source._format, self.target, self.target._format)
2866
 
        f = RepoFetcher(to_repository=self.target,
2867
 
                               from_repository=self.source,
2868
 
                               last_revision=revision_id,
2869
 
                               pb=pb, find_ghosts=find_ghosts)
2870
 
        return f.count_copied, f.failed_revisions
2871
 
 
2872
3198
    @needs_read_lock
2873
3199
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2874
3200
        """See InterRepository.missing_revision_ids()."""
2875
3201
        # we want all revisions to satisfy revision_id in source.
2876
3202
        # but we don't want to stat every file here and there.
2877
 
        # we want then, all revisions other needs to satisfy revision_id 
 
3203
        # we want then, all revisions other needs to satisfy revision_id
2878
3204
        # checked, but not those that we have locally.
2879
 
        # so the first thing is to get a subset of the revisions to 
 
3205
        # so the first thing is to get a subset of the revisions to
2880
3206
        # satisfy revision_id in source, and then eliminate those that
2881
 
        # we do already have. 
 
3207
        # we do already have.
2882
3208
        # this is slow on high latency connection to self, but as this
2883
 
        # disk format scales terribly for push anyway due to rewriting 
 
3209
        # disk format scales terribly for push anyway due to rewriting
2884
3210
        # inventory.weave, this is considered acceptable.
2885
3211
        # - RBC 20060209
2886
3212
        if revision_id is not None:
2906
3232
            # and the tip revision was validated by get_ancestry.
2907
3233
            result_set = required_revisions
2908
3234
        else:
2909
 
            # if we just grabbed the possibly available ids, then 
 
3235
            # if we just grabbed the possibly available ids, then
2910
3236
            # we only have an estimate of whats available and need to validate
2911
3237
            # that against the revision records.
2912
3238
            result_set = set(
2925
3251
    @staticmethod
2926
3252
    def is_compatible(source, target):
2927
3253
        """Be compatible with known Knit formats.
2928
 
        
 
3254
 
2929
3255
        We don't test for the stores being of specific types because that
2930
 
        could lead to confusing results, and there is no need to be 
 
3256
        could lead to confusing results, and there is no need to be
2931
3257
        overly general.
2932
3258
        """
2933
3259
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
2938
3264
            return False
2939
3265
        return are_knits and InterRepository._same_model(source, target)
2940
3266
 
2941
 
    @needs_write_lock
2942
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2943
 
        """See InterRepository.fetch()."""
2944
 
        from bzrlib.fetch import RepoFetcher
2945
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2946
 
               self.source, self.source._format, self.target, self.target._format)
2947
 
        f = RepoFetcher(to_repository=self.target,
2948
 
                            from_repository=self.source,
2949
 
                            last_revision=revision_id,
2950
 
                            pb=pb, find_ghosts=find_ghosts)
2951
 
        return f.count_copied, f.failed_revisions
2952
 
 
2953
3267
    @needs_read_lock
2954
3268
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2955
3269
        """See InterRepository.missing_revision_ids()."""
2976
3290
            # and the tip revision was validated by get_ancestry.
2977
3291
            result_set = required_revisions
2978
3292
        else:
2979
 
            # if we just grabbed the possibly available ids, then 
 
3293
            # if we just grabbed the possibly available ids, then
2980
3294
            # we only have an estimate of whats available and need to validate
2981
3295
            # that against the revision records.
2982
3296
            result_set = set(
2995
3309
    @staticmethod
2996
3310
    def is_compatible(source, target):
2997
3311
        """Be compatible with known Pack formats.
2998
 
        
 
3312
 
2999
3313
        We don't test for the stores being of specific types because that
3000
 
        could lead to confusing results, and there is no need to be 
 
3314
        could lead to confusing results, and there is no need to be
3001
3315
        overly general.
3002
3316
        """
3003
3317
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
3009
3323
        return are_packs and InterRepository._same_model(source, target)
3010
3324
 
3011
3325
    @needs_write_lock
3012
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
3326
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3327
            fetch_spec=None):
3013
3328
        """See InterRepository.fetch()."""
3014
3329
        if (len(self.source._fallback_repositories) > 0 or
3015
3330
            len(self.target._fallback_repositories) > 0):
3019
3334
            # attributes on repository.
3020
3335
            from bzrlib.fetch import RepoFetcher
3021
3336
            fetcher = RepoFetcher(self.target, self.source, revision_id,
3022
 
                                  pb, find_ghosts)
3023
 
            return fetcher.count_copied, fetcher.failed_revisions
3024
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
3025
 
               self.source, self.source._format, self.target, self.target._format)
3026
 
        self.count_copied = 0
 
3337
                    pb, find_ghosts, fetch_spec=fetch_spec)
 
3338
        if fetch_spec is not None:
 
3339
            if len(list(fetch_spec.heads)) != 1:
 
3340
                raise AssertionError(
 
3341
                    "InterPackRepo.fetch doesn't support "
 
3342
                    "fetching multiple heads yet.")
 
3343
            revision_id = list(fetch_spec.heads)[0]
 
3344
            fetch_spec = None
3027
3345
        if revision_id is None:
3028
3346
            # TODO:
3029
3347
            # everything to do - use pack logic
3032
3350
            # till then:
3033
3351
            source_revision_ids = frozenset(self.source.all_revision_ids())
3034
3352
            revision_ids = source_revision_ids - \
3035
 
                frozenset(self.target_get_parent_map(source_revision_ids))
 
3353
                frozenset(self.target.get_parent_map(source_revision_ids))
3036
3354
            revision_keys = [(revid,) for revid in revision_ids]
3037
 
            target_pack_collection = self._get_target_pack_collection()
3038
 
            index = target_pack_collection.revision_index.combined_index
 
3355
            index = self.target._pack_collection.revision_index.combined_index
3039
3356
            present_revision_ids = set(item[1][0] for item in
3040
3357
                index.iter_entries(revision_keys))
3041
3358
            revision_ids = set(revision_ids) - present_revision_ids
3061
3378
 
3062
3379
    def _pack(self, source, target, revision_ids):
3063
3380
        from bzrlib.repofmt.pack_repo import Packer
3064
 
        target_pack_collection = self._get_target_pack_collection()
3065
3381
        packs = source._pack_collection.all_packs()
3066
 
        pack = Packer(target_pack_collection, packs, '.fetch',
 
3382
        pack = Packer(self.target._pack_collection, packs, '.fetch',
3067
3383
            revision_ids).pack()
3068
3384
        if pack is not None:
3069
 
            target_pack_collection._save_pack_names()
 
3385
            self.target._pack_collection._save_pack_names()
3070
3386
            copied_revs = pack.get_revision_count()
3071
3387
            # Trigger an autopack. This may duplicate effort as we've just done
3072
3388
            # a pack creation, but for now it is simpler to think about as
3073
3389
            # 'upload data, then repack if needed'.
3074
 
            self._autopack()
 
3390
            self.target._pack_collection.autopack()
3075
3391
            return (copied_revs, [])
3076
3392
        else:
3077
3393
            return (0, [])
3078
3394
 
3079
 
    def _autopack(self):
3080
 
        self.target._pack_collection.autopack()
3081
 
        
3082
 
    def _get_target_pack_collection(self):
3083
 
        return self.target._pack_collection
3084
 
 
3085
3395
    @needs_read_lock
3086
3396
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3087
3397
        """See InterRepository.missing_revision_ids().
3088
 
        
 
3398
 
3089
3399
        :param find_ghosts: Find ghosts throughout the ancestry of
3090
3400
            revision_id.
3091
3401
        """
3094
3404
        elif revision_id is not None:
3095
3405
            # Find ghosts: search for revisions pointing from one repository to
3096
3406
            # the other, and vice versa, anywhere in the history of revision_id.
3097
 
            graph = self.target_get_graph(other_repository=self.source)
 
3407
            graph = self.target.get_graph(other_repository=self.source)
3098
3408
            searcher = graph._make_breadth_first_searcher([revision_id])
3099
3409
            found_ids = set()
3100
3410
            while True:
3110
3420
            # Double query here: should be able to avoid this by changing the
3111
3421
            # graph api further.
3112
3422
            result_set = found_ids - frozenset(
3113
 
                self.target_get_parent_map(found_ids))
 
3423
                self.target.get_parent_map(found_ids))
3114
3424
        else:
3115
3425
            source_ids = self.source.all_revision_ids()
3116
3426
            # source_ids is the worst possible case we may need to pull.
3122
3432
        return self.source.revision_ids_to_search_result(result_set)
3123
3433
 
3124
3434
 
3125
 
class InterModel1and2(InterRepository):
3126
 
 
3127
 
    @classmethod
3128
 
    def _get_repo_format_to_test(self):
3129
 
        return None
3130
 
 
3131
 
    @staticmethod
3132
 
    def is_compatible(source, target):
3133
 
        if not source.supports_rich_root() and target.supports_rich_root():
3134
 
            return True
3135
 
        else:
3136
 
            return False
3137
 
 
3138
 
    @needs_write_lock
3139
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3140
 
        """See InterRepository.fetch()."""
3141
 
        from bzrlib.fetch import Model1toKnit2Fetcher
3142
 
        f = Model1toKnit2Fetcher(to_repository=self.target,
3143
 
                                 from_repository=self.source,
3144
 
                                 last_revision=revision_id,
3145
 
                                 pb=pb, find_ghosts=find_ghosts)
3146
 
        return f.count_copied, f.failed_revisions
3147
 
 
3148
 
    @needs_write_lock
3149
 
    def copy_content(self, revision_id=None):
3150
 
        """Make a complete copy of the content in self into destination.
3151
 
        
3152
 
        This is a destructive operation! Do not use it on existing 
3153
 
        repositories.
3154
 
 
3155
 
        :param revision_id: Only copy the content needed to construct
3156
 
                            revision_id and its parents.
3157
 
        """
3158
 
        try:
3159
 
            self.target.set_make_working_trees(self.source.make_working_trees())
3160
 
        except NotImplementedError:
3161
 
            pass
3162
 
        # but don't bother fetching if we have the needed data now.
3163
 
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
3164
 
            self.target.has_revision(revision_id)):
3165
 
            return
3166
 
        self.target.fetch(self.source, revision_id=revision_id)
3167
 
 
3168
 
 
3169
 
class InterKnit1and2(InterKnitRepo):
3170
 
 
3171
 
    @classmethod
3172
 
    def _get_repo_format_to_test(self):
3173
 
        return None
3174
 
 
3175
 
    @staticmethod
3176
 
    def is_compatible(source, target):
3177
 
        """Be compatible with Knit1 source and Knit3 target"""
3178
 
        try:
3179
 
            from bzrlib.repofmt.knitrepo import (
3180
 
                RepositoryFormatKnit1,
3181
 
                RepositoryFormatKnit3,
3182
 
                )
3183
 
            from bzrlib.repofmt.pack_repo import (
3184
 
                RepositoryFormatKnitPack1,
3185
 
                RepositoryFormatKnitPack3,
3186
 
                RepositoryFormatKnitPack4,
3187
 
                RepositoryFormatKnitPack5,
3188
 
                RepositoryFormatKnitPack5RichRoot,
3189
 
                RepositoryFormatKnitPack6,
3190
 
                RepositoryFormatKnitPack6RichRoot,
3191
 
                RepositoryFormatPackDevelopment2,
3192
 
                RepositoryFormatPackDevelopment2Subtree,
3193
 
                )
3194
 
            norichroot = (
3195
 
                RepositoryFormatKnit1,            # no rr, no subtree
3196
 
                RepositoryFormatKnitPack1,        # no rr, no subtree
3197
 
                RepositoryFormatPackDevelopment2, # no rr, no subtree
3198
 
                RepositoryFormatKnitPack5,        # no rr, no subtree
3199
 
                RepositoryFormatKnitPack6,        # no rr, no subtree
3200
 
                )
3201
 
            richroot = (
3202
 
                RepositoryFormatKnit3,            # rr, subtree
3203
 
                RepositoryFormatKnitPack3,        # rr, subtree
3204
 
                RepositoryFormatKnitPack4,        # rr, no subtree
3205
 
                RepositoryFormatKnitPack5RichRoot,# rr, no subtree
3206
 
                RepositoryFormatKnitPack6RichRoot,# rr, no subtree
3207
 
                RepositoryFormatPackDevelopment2Subtree, # rr, subtree
3208
 
                )
3209
 
            for format in norichroot:
3210
 
                if format.rich_root_data:
3211
 
                    raise AssertionError('Format %s is a rich-root format'
3212
 
                        ' but is included in the non-rich-root list'
3213
 
                        % (format,))
3214
 
            for format in richroot:
3215
 
                if not format.rich_root_data:
3216
 
                    raise AssertionError('Format %s is not a rich-root format'
3217
 
                        ' but is included in the rich-root list'
3218
 
                        % (format,))
3219
 
            # TODO: One alternative is to just check format.rich_root_data,
3220
 
            #       instead of keeping membership lists. However, the formats
3221
 
            #       *also* have to use the same 'Knit' style of storage
3222
 
            #       (line-deltas, fulltexts, etc.)
3223
 
            return (isinstance(source._format, norichroot) and
3224
 
                    isinstance(target._format, richroot))
3225
 
        except AttributeError:
3226
 
            return False
3227
 
 
3228
 
    @needs_write_lock
3229
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3230
 
        """See InterRepository.fetch()."""
3231
 
        from bzrlib.fetch import Knit1to2Fetcher
3232
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
3233
 
               self.source, self.source._format, self.target, 
3234
 
               self.target._format)
3235
 
        f = Knit1to2Fetcher(to_repository=self.target,
3236
 
                            from_repository=self.source,
3237
 
                            last_revision=revision_id,
3238
 
                            pb=pb, find_ghosts=find_ghosts)
3239
 
        return f.count_copied, f.failed_revisions
3240
 
 
3241
 
 
3242
3435
class InterDifferingSerializer(InterKnitRepo):
3243
3436
 
3244
3437
    @classmethod
3257
3450
            return False
3258
3451
        return True
3259
3452
 
3260
 
    def _fetch_batch(self, revision_ids, basis_id, basis_tree):
 
3453
    def _get_delta_for_revision(self, tree, parent_ids, basis_id, cache):
 
3454
        """Get the best delta and base for this revision.
 
3455
 
 
3456
        :return: (basis_id, delta)
 
3457
        """
 
3458
        possible_trees = [(parent_id, cache[parent_id])
 
3459
                          for parent_id in parent_ids
 
3460
                           if parent_id in cache]
 
3461
        if len(possible_trees) == 0:
 
3462
            # There either aren't any parents, or the parents aren't in the
 
3463
            # cache, so just use the last converted tree
 
3464
            possible_trees.append((basis_id, cache[basis_id]))
 
3465
        deltas = []
 
3466
        for basis_id, basis_tree in possible_trees:
 
3467
            delta = tree.inventory._make_delta(basis_tree.inventory)
 
3468
            deltas.append((len(delta), basis_id, delta))
 
3469
        deltas.sort()
 
3470
        return deltas[0][1:]
 
3471
 
 
3472
    def _fetch_batch(self, revision_ids, basis_id, cache):
3261
3473
        """Fetch across a few revisions.
3262
3474
 
3263
3475
        :param revision_ids: The revisions to copy
3264
 
        :param basis_id: The revision_id of basis_tree
3265
 
        :param basis_tree: A tree that is not in revision_ids which should
3266
 
            already exist in the target.
3267
 
        :return: (basis_id, basis_tree) A new basis to use now that these trees
3268
 
            have been copied.
 
3476
        :param basis_id: The revision_id of a tree that must be in cache, used
 
3477
            as a basis for delta when no other base is available
 
3478
        :param cache: A cache of RevisionTrees that we can use.
 
3479
        :return: The revision_id of the last converted tree. The RevisionTree
 
3480
            for it will be in cache
3269
3481
        """
3270
3482
        # Walk though all revisions; get inventory deltas, copy referenced
3271
3483
        # texts that delta references, insert the delta, revision and
3273
3485
        text_keys = set()
3274
3486
        pending_deltas = []
3275
3487
        pending_revisions = []
 
3488
        parent_map = self.source.get_parent_map(revision_ids)
3276
3489
        for tree in self.source.revision_trees(revision_ids):
3277
3490
            current_revision_id = tree.get_revision_id()
3278
 
            delta = tree.inventory._make_delta(basis_tree.inventory)
 
3491
            parent_ids = parent_map.get(current_revision_id, ())
 
3492
            basis_id, delta = self._get_delta_for_revision(tree, parent_ids,
 
3493
                                                           basis_id, cache)
 
3494
            # Find text entries that need to be copied
3279
3495
            for old_path, new_path, file_id, entry in delta:
3280
3496
                if new_path is not None:
3281
3497
                    if not (new_path or self.target.supports_rich_root()):
3282
 
                        # We leave the inventory delta in, because that
3283
 
                        # will have the deserialised inventory root
3284
 
                        # pointer.
 
3498
                        # We don't copy the text for the root node unless the
 
3499
                        # target supports_rich_root.
3285
3500
                        continue
3286
 
                    # TODO: Do we need:
3287
 
                    #       "if entry.revision == current_revision_id" ?
3288
 
                    if entry.revision == current_revision_id:
3289
 
                        text_keys.add((file_id, entry.revision))
 
3501
                    text_keys.add((file_id, entry.revision))
3290
3502
            revision = self.source.get_revision(current_revision_id)
3291
3503
            pending_deltas.append((basis_id, delta,
3292
3504
                current_revision_id, revision.parent_ids))
3293
3505
            pending_revisions.append(revision)
 
3506
            cache[current_revision_id] = tree
3294
3507
            basis_id = current_revision_id
3295
 
            basis_tree = tree
3296
3508
        # Copy file texts
3297
3509
        from_texts = self.source.texts
3298
3510
        to_texts = self.target.texts
3299
3511
        to_texts.insert_record_stream(from_texts.get_record_stream(
3300
 
            text_keys, self.target._fetch_order,
3301
 
            not self.target._fetch_uses_deltas))
 
3512
            text_keys, self.target._format._fetch_order,
 
3513
            not self.target._format._fetch_uses_deltas))
3302
3514
        # insert deltas
3303
3515
        for delta in pending_deltas:
3304
3516
            self.target.add_inventory_by_delta(*delta)
3312
3524
            except errors.NoSuchRevision:
3313
3525
                pass
3314
3526
            self.target.add_revision(revision.revision_id, revision)
3315
 
        return basis_id, basis_tree
 
3527
        return basis_id
3316
3528
 
3317
3529
    def _fetch_all_revisions(self, revision_ids, pb):
3318
3530
        """Fetch everything for the list of revisions.
3324
3536
        """
3325
3537
        basis_id, basis_tree = self._get_basis(revision_ids[0])
3326
3538
        batch_size = 100
 
3539
        cache = lru_cache.LRUCache(100)
 
3540
        cache[basis_id] = basis_tree
 
3541
        del basis_tree # We don't want to hang on to it here
3327
3542
        for offset in range(0, len(revision_ids), batch_size):
3328
3543
            self.target.start_write_group()
3329
3544
            try:
3330
3545
                pb.update('Transferring revisions', offset,
3331
3546
                          len(revision_ids))
3332
3547
                batch = revision_ids[offset:offset+batch_size]
3333
 
                basis_id, basis_tree = self._fetch_batch(batch,
3334
 
                    basis_id, basis_tree)
 
3548
                basis_id = self._fetch_batch(batch, basis_id, cache)
3335
3549
            except:
3336
3550
                self.target.abort_write_group()
3337
3551
                raise
3341
3555
                  len(revision_ids))
3342
3556
 
3343
3557
    @needs_write_lock
3344
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
3558
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3559
            fetch_spec=None):
3345
3560
        """See InterRepository.fetch()."""
 
3561
        if fetch_spec is not None:
 
3562
            raise AssertionError("Not implemented yet...")
3346
3563
        revision_ids = self.target.search_missing_revision_ids(self.source,
3347
3564
            revision_id, find_ghosts=find_ghosts).get_keys()
3348
3565
        if not revision_ids:
3353
3570
            my_pb = ui.ui_factory.nested_progress_bar()
3354
3571
            pb = my_pb
3355
3572
        else:
 
3573
            symbol_versioning.warn(
 
3574
                symbol_versioning.deprecated_in((1, 14, 0))
 
3575
                % "pb parameter to fetch()")
3356
3576
            my_pb = None
3357
3577
        try:
3358
3578
            self._fetch_all_revisions(revision_ids, pb)
3384
3604
        return basis_id, basis_tree
3385
3605
 
3386
3606
 
3387
 
class InterOtherToRemote(InterRepository):
3388
 
    """An InterRepository that simply delegates to the 'real' InterRepository
3389
 
    calculated for (source, target._real_repository).
3390
 
    """
3391
 
 
3392
 
    _walk_to_common_revisions_batch_size = 50
3393
 
 
3394
 
    def __init__(self, source, target):
3395
 
        InterRepository.__init__(self, source, target)
3396
 
        self._real_inter = None
3397
 
 
3398
 
    @staticmethod
3399
 
    def is_compatible(source, target):
3400
 
        if isinstance(target, remote.RemoteRepository):
3401
 
            return True
3402
 
        return False
3403
 
 
3404
 
    def _ensure_real_inter(self):
3405
 
        if self._real_inter is None:
3406
 
            self.target._ensure_real()
3407
 
            real_target = self.target._real_repository
3408
 
            self._real_inter = InterRepository.get(self.source, real_target)
3409
 
            # Make _real_inter use the RemoteRepository for get_parent_map
3410
 
            self._real_inter.target_get_graph = self.target.get_graph
3411
 
            self._real_inter.target_get_parent_map = self.target.get_parent_map
3412
 
    
3413
 
    def copy_content(self, revision_id=None):
3414
 
        self._ensure_real_inter()
3415
 
        self._real_inter.copy_content(revision_id=revision_id)
3416
 
 
3417
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3418
 
        self._ensure_real_inter()
3419
 
        return self._real_inter.fetch(revision_id=revision_id, pb=pb,
3420
 
            find_ghosts=find_ghosts)
3421
 
 
3422
 
    @classmethod
3423
 
    def _get_repo_format_to_test(self):
3424
 
        return None
3425
 
 
3426
 
 
3427
 
class InterRemoteToOther(InterRepository):
3428
 
 
3429
 
    def __init__(self, source, target):
3430
 
        InterRepository.__init__(self, source, target)
3431
 
        self._real_inter = None
3432
 
 
3433
 
    @staticmethod
3434
 
    def is_compatible(source, target):
3435
 
        if not isinstance(source, remote.RemoteRepository):
3436
 
            return False
3437
 
        # Is source's model compatible with target's model?
3438
 
        source._ensure_real()
3439
 
        real_source = source._real_repository
3440
 
        if isinstance(real_source, remote.RemoteRepository):
3441
 
            raise NotImplementedError(
3442
 
                "We don't support remote repos backed by remote repos yet.")
3443
 
        return InterRepository._same_model(real_source, target)
3444
 
 
3445
 
    def _ensure_real_inter(self):
3446
 
        if self._real_inter is None:
3447
 
            self.source._ensure_real()
3448
 
            real_source = self.source._real_repository
3449
 
            self._real_inter = InterRepository.get(real_source, self.target)
3450
 
    
3451
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3452
 
        self._ensure_real_inter()
3453
 
        return self._real_inter.fetch(revision_id=revision_id, pb=pb,
3454
 
            find_ghosts=find_ghosts)
3455
 
 
3456
 
    def copy_content(self, revision_id=None):
3457
 
        self._ensure_real_inter()
3458
 
        self._real_inter.copy_content(revision_id=revision_id)
3459
 
 
3460
 
    @classmethod
3461
 
    def _get_repo_format_to_test(self):
3462
 
        return None
3463
 
 
3464
 
 
3465
 
 
3466
 
class InterPackToRemotePack(InterPackRepo):
3467
 
    """A specialisation of InterPackRepo for a target that is a
3468
 
    RemoteRepository.
3469
 
 
3470
 
    This will use the get_parent_map RPC rather than plain readvs, and also
3471
 
    uses an RPC for autopacking.
3472
 
    """
3473
 
 
3474
 
    _walk_to_common_revisions_batch_size = 50
3475
 
 
3476
 
    @staticmethod
3477
 
    def is_compatible(source, target):
3478
 
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
3479
 
        if isinstance(source._format, RepositoryFormatPack):
3480
 
            if isinstance(target, remote.RemoteRepository):
3481
 
                target._ensure_real()
3482
 
                if isinstance(target._real_repository._format,
3483
 
                              RepositoryFormatPack):
3484
 
                    if InterRepository._same_model(source, target):
3485
 
                        return True
3486
 
        return False
3487
 
    
3488
 
    def _autopack(self):
3489
 
        self.target.autopack()
3490
 
 
3491
 
    @needs_write_lock
3492
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3493
 
        """See InterRepository.fetch()."""
3494
 
        # Always fetch using the generic streaming fetch code, to allow
3495
 
        # streaming fetching into remote servers.
3496
 
        from bzrlib.fetch import RepoFetcher
3497
 
        fetcher = RepoFetcher(self.target, self.source, revision_id,
3498
 
                              pb, find_ghosts)
3499
 
        self.target.autopack()
3500
 
        return fetcher.count_copied, fetcher.failed_revisions
3501
 
        
3502
 
    def _get_target_pack_collection(self):
3503
 
        return self.target._real_repository._pack_collection
3504
 
 
3505
 
    @classmethod
3506
 
    def _get_repo_format_to_test(self):
3507
 
        return None
3508
 
 
3509
 
 
3510
3607
InterRepository.register_optimiser(InterDifferingSerializer)
3511
3608
InterRepository.register_optimiser(InterSameDataRepository)
3512
3609
InterRepository.register_optimiser(InterWeaveRepo)
3513
3610
InterRepository.register_optimiser(InterKnitRepo)
3514
 
InterRepository.register_optimiser(InterModel1and2)
3515
 
InterRepository.register_optimiser(InterKnit1and2)
3516
3611
InterRepository.register_optimiser(InterPackRepo)
3517
 
InterRepository.register_optimiser(InterOtherToRemote)
3518
 
InterRepository.register_optimiser(InterRemoteToOther)
3519
 
InterRepository.register_optimiser(InterPackToRemotePack)
3520
3612
 
3521
3613
 
3522
3614
class CopyConverter(object):
3523
3615
    """A repository conversion tool which just performs a copy of the content.
3524
 
    
 
3616
 
3525
3617
    This is slow but quite reliable.
3526
3618
    """
3527
3619
 
3531
3623
        :param target_format: The format the resulting repository should be.
3532
3624
        """
3533
3625
        self.target_format = target_format
3534
 
        
 
3626
 
3535
3627
    def convert(self, repo, pb):
3536
3628
        """Perform the conversion of to_convert, giving feedback via pb.
3537
3629
 
3603
3695
 
3604
3696
class _VersionedFileChecker(object):
3605
3697
 
3606
 
    def __init__(self, repository):
 
3698
    def __init__(self, repository, text_key_references=None):
3607
3699
        self.repository = repository
3608
 
        self.text_index = self.repository._generate_text_key_index()
3609
 
    
 
3700
        self.text_index = self.repository._generate_text_key_index(
 
3701
            text_key_references=text_key_references)
 
3702
 
3610
3703
    def calculate_file_version_parents(self, text_key):
3611
3704
        """Calculate the correct parents for a file version according to
3612
3705
        the inventories.
3687
3780
    def __init__(self, target_repo):
3688
3781
        self.target_repo = target_repo
3689
3782
 
3690
 
    def insert_stream(self, stream, src_format):
 
3783
    def insert_stream(self, stream, src_format, resume_tokens):
3691
3784
        """Insert a stream's content into the target repository.
3692
3785
 
3693
3786
        :param src_format: a bzr repository format.
3694
3787
 
3695
 
        :return: an iterable of keys additional items required before the
3696
 
        insertion can be completed.
 
3788
        :return: a list of resume tokens and an  iterable of keys additional
 
3789
            items required before the insertion can be completed.
3697
3790
        """
3698
 
        result = []
 
3791
        self.target_repo.lock_write()
 
3792
        try:
 
3793
            if resume_tokens:
 
3794
                self.target_repo.resume_write_group(resume_tokens)
 
3795
            else:
 
3796
                self.target_repo.start_write_group()
 
3797
            try:
 
3798
                # locked_insert_stream performs a commit|suspend.
 
3799
                return self._locked_insert_stream(stream, src_format)
 
3800
            except:
 
3801
                self.target_repo.abort_write_group(suppress_errors=True)
 
3802
                raise
 
3803
        finally:
 
3804
            self.target_repo.unlock()
 
3805
 
 
3806
    def _locked_insert_stream(self, stream, src_format):
3699
3807
        to_serializer = self.target_repo._format._serializer
3700
3808
        src_serializer = src_format._serializer
 
3809
        if to_serializer == src_serializer:
 
3810
            # If serializers match and the target is a pack repository, set the
 
3811
            # write cache size on the new pack.  This avoids poor performance
 
3812
            # on transports where append is unbuffered (such as
 
3813
            # RemoteTransport).  This is safe to do because nothing should read
 
3814
            # back from the target repository while a stream with matching
 
3815
            # serialization is being inserted.
 
3816
            # The exception is that a delta record from the source that should
 
3817
            # be a fulltext may need to be expanded by the target (see
 
3818
            # test_fetch_revisions_with_deltas_into_pack); but we take care to
 
3819
            # explicitly flush any buffered writes first in that rare case.
 
3820
            try:
 
3821
                new_pack = self.target_repo._pack_collection._new_pack
 
3822
            except AttributeError:
 
3823
                # Not a pack repository
 
3824
                pass
 
3825
            else:
 
3826
                new_pack.set_write_cache_size(1024*1024)
3701
3827
        for substream_type, substream in stream:
3702
3828
            if substream_type == 'texts':
3703
3829
                self.target_repo.texts.insert_record_stream(substream)
3722
3848
                self.target_repo.signatures.insert_record_stream(substream)
3723
3849
            else:
3724
3850
                raise AssertionError('kaboom! %s' % (substream_type,))
3725
 
        return result
 
3851
        try:
 
3852
            missing_keys = set()
 
3853
            for prefix, versioned_file in (
 
3854
                ('texts', self.target_repo.texts),
 
3855
                ('inventories', self.target_repo.inventories),
 
3856
                ('revisions', self.target_repo.revisions),
 
3857
                ('signatures', self.target_repo.signatures),
 
3858
                ):
 
3859
                missing_keys.update((prefix,) + key for key in
 
3860
                    versioned_file.get_missing_compression_parent_keys())
 
3861
        except NotImplementedError:
 
3862
            # cannot even attempt suspending, and missing would have failed
 
3863
            # during stream insertion.
 
3864
            missing_keys = set()
 
3865
        else:
 
3866
            if missing_keys:
 
3867
                # suspend the write group and tell the caller what we is
 
3868
                # missing. We know we can suspend or else we would not have
 
3869
                # entered this code path. (All repositories that can handle
 
3870
                # missing keys can handle suspending a write group).
 
3871
                write_group_tokens = self.target_repo.suspend_write_group()
 
3872
                return write_group_tokens, missing_keys
 
3873
        self.target_repo.commit_write_group()
 
3874
        return [], set()
3726
3875
 
3727
3876
    def _extract_and_insert_inventories(self, substream, serializer):
3728
3877
        """Generate a new inventory versionedfile in target, converting data.
3729
 
        
 
3878
 
3730
3879
        The inventory is retrieved from the source, (deserializing it), and
3731
3880
        stored in the target (reserializing it in a different format).
3732
3881
        """
3747
3896
            self.target_repo.add_revision(revision_id, rev)
3748
3897
 
3749
3898
    def finished(self):
3750
 
        if self.target_repo._fetch_reconcile:
 
3899
        if self.target_repo._format._fetch_reconcile:
3751
3900
            self.target_repo.reconcile()
3752
3901
 
 
3902
 
 
3903
class StreamSource(object):
 
3904
    """A source of a stream for fetching between repositories."""
 
3905
 
 
3906
    def __init__(self, from_repository, to_format):
 
3907
        """Create a StreamSource streaming from from_repository."""
 
3908
        self.from_repository = from_repository
 
3909
        self.to_format = to_format
 
3910
 
 
3911
    def delta_on_metadata(self):
 
3912
        """Return True if delta's are permitted on metadata streams.
 
3913
 
 
3914
        That is on revisions and signatures.
 
3915
        """
 
3916
        src_serializer = self.from_repository._format._serializer
 
3917
        target_serializer = self.to_format._serializer
 
3918
        return (self.to_format._fetch_uses_deltas and
 
3919
            src_serializer == target_serializer)
 
3920
 
 
3921
    def _fetch_revision_texts(self, revs):
 
3922
        # fetch signatures first and then the revision texts
 
3923
        # may need to be a InterRevisionStore call here.
 
3924
        from_sf = self.from_repository.signatures
 
3925
        # A missing signature is just skipped.
 
3926
        keys = [(rev_id,) for rev_id in revs]
 
3927
        signatures = versionedfile.filter_absent(from_sf.get_record_stream(
 
3928
            keys,
 
3929
            self.to_format._fetch_order,
 
3930
            not self.to_format._fetch_uses_deltas))
 
3931
        # If a revision has a delta, this is actually expanded inside the
 
3932
        # insert_record_stream code now, which is an alternate fix for
 
3933
        # bug #261339
 
3934
        from_rf = self.from_repository.revisions
 
3935
        revisions = from_rf.get_record_stream(
 
3936
            keys,
 
3937
            self.to_format._fetch_order,
 
3938
            not self.delta_on_metadata())
 
3939
        return [('signatures', signatures), ('revisions', revisions)]
 
3940
 
 
3941
    def _generate_root_texts(self, revs):
 
3942
        """This will be called by __fetch between fetching weave texts and
 
3943
        fetching the inventory weave.
 
3944
 
 
3945
        Subclasses should override this if they need to generate root texts
 
3946
        after fetching weave texts.
 
3947
        """
 
3948
        if self._rich_root_upgrade():
 
3949
            import bzrlib.fetch
 
3950
            return bzrlib.fetch.Inter1and2Helper(
 
3951
                self.from_repository).generate_root_texts(revs)
 
3952
        else:
 
3953
            return []
 
3954
 
 
3955
    def get_stream(self, search):
 
3956
        phase = 'file'
 
3957
        revs = search.get_keys()
 
3958
        graph = self.from_repository.get_graph()
 
3959
        revs = list(graph.iter_topo_order(revs))
 
3960
        data_to_fetch = self.from_repository.item_keys_introduced_by(revs)
 
3961
        text_keys = []
 
3962
        for knit_kind, file_id, revisions in data_to_fetch:
 
3963
            if knit_kind != phase:
 
3964
                phase = knit_kind
 
3965
                # Make a new progress bar for this phase
 
3966
            if knit_kind == "file":
 
3967
                # Accumulate file texts
 
3968
                text_keys.extend([(file_id, revision) for revision in
 
3969
                    revisions])
 
3970
            elif knit_kind == "inventory":
 
3971
                # Now copy the file texts.
 
3972
                from_texts = self.from_repository.texts
 
3973
                yield ('texts', from_texts.get_record_stream(
 
3974
                    text_keys, self.to_format._fetch_order,
 
3975
                    not self.to_format._fetch_uses_deltas))
 
3976
                # Cause an error if a text occurs after we have done the
 
3977
                # copy.
 
3978
                text_keys = None
 
3979
                # Before we process the inventory we generate the root
 
3980
                # texts (if necessary) so that the inventories references
 
3981
                # will be valid.
 
3982
                for _ in self._generate_root_texts(revs):
 
3983
                    yield _
 
3984
                # NB: This currently reopens the inventory weave in source;
 
3985
                # using a single stream interface instead would avoid this.
 
3986
                from_weave = self.from_repository.inventories
 
3987
                # we fetch only the referenced inventories because we do not
 
3988
                # know for unselected inventories whether all their required
 
3989
                # texts are present in the other repository - it could be
 
3990
                # corrupt.
 
3991
                yield ('inventories', from_weave.get_record_stream(
 
3992
                    [(rev_id,) for rev_id in revs],
 
3993
                    self.inventory_fetch_order(),
 
3994
                    not self.delta_on_metadata()))
 
3995
            elif knit_kind == "signatures":
 
3996
                # Nothing to do here; this will be taken care of when
 
3997
                # _fetch_revision_texts happens.
 
3998
                pass
 
3999
            elif knit_kind == "revisions":
 
4000
                for record in self._fetch_revision_texts(revs):
 
4001
                    yield record
 
4002
            else:
 
4003
                raise AssertionError("Unknown knit kind %r" % knit_kind)
 
4004
 
 
4005
    def get_stream_for_missing_keys(self, missing_keys):
 
4006
        # missing keys can only occur when we are byte copying and not
 
4007
        # translating (because translation means we don't send
 
4008
        # unreconstructable deltas ever).
 
4009
        keys = {}
 
4010
        keys['texts'] = set()
 
4011
        keys['revisions'] = set()
 
4012
        keys['inventories'] = set()
 
4013
        keys['signatures'] = set()
 
4014
        for key in missing_keys:
 
4015
            keys[key[0]].add(key[1:])
 
4016
        if len(keys['revisions']):
 
4017
            # If we allowed copying revisions at this point, we could end up
 
4018
            # copying a revision without copying its required texts: a
 
4019
            # violation of the requirements for repository integrity.
 
4020
            raise AssertionError(
 
4021
                'cannot copy revisions to fill in missing deltas %s' % (
 
4022
                    keys['revisions'],))
 
4023
        for substream_kind, keys in keys.iteritems():
 
4024
            vf = getattr(self.from_repository, substream_kind)
 
4025
            # Ask for full texts always so that we don't need more round trips
 
4026
            # after this stream.
 
4027
            stream = vf.get_record_stream(keys,
 
4028
                self.to_format._fetch_order, True)
 
4029
            yield substream_kind, stream
 
4030
 
 
4031
    def inventory_fetch_order(self):
 
4032
        if self._rich_root_upgrade():
 
4033
            return 'topological'
 
4034
        else:
 
4035
            return self.to_format._fetch_order
 
4036
 
 
4037
    def _rich_root_upgrade(self):
 
4038
        return (not self.from_repository._format.rich_root_data and
 
4039
            self.to_format.rich_root_data)
 
4040