~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-03-06 06:48:25 UTC
  • mfrom: (4070.8.6 debug-config)
  • Revision ID: pqm@pqm.ubuntu.com-20090306064825-kbpwggw21dygeix6
(mbp) debug_flags configuration option

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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,
37
38
    revision as _mod_revision,
38
39
    symbol_versioning,
39
40
    tsort,
48
49
 
49
50
from bzrlib.decorators import needs_read_lock, needs_write_lock
50
51
from bzrlib.inter import InterObject
51
 
from bzrlib.inventory import (
52
 
    Inventory,
53
 
    InventoryDirectory,
54
 
    ROOT_ID,
55
 
    entry_factory,
56
 
    )
 
52
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
57
53
from bzrlib import registry
58
54
from bzrlib.symbol_versioning import (
59
55
        deprecated_method,
 
56
        one_one,
 
57
        one_two,
 
58
        one_six,
60
59
        )
61
60
from bzrlib.trace import (
62
61
    log_exception_quietly, note, mutter, mutter_callsite, warning)
127
126
        # valid. Callers that will call record_delete() should call
128
127
        # .will_record_deletes() to indicate that.
129
128
        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
142
129
 
143
130
    def _validate_unicode_text(self, text, context):
144
131
        """Verify things like commit messages don't have bogus characters."""
189
176
        deserializing the inventory, while we already have a copy in
190
177
        memory.
191
178
        """
192
 
        if self.new_inventory is None:
193
 
            self.new_inventory = self.repository.get_inventory(
194
 
                self._new_revision_id)
195
179
        return RevisionTree(self.repository, self.new_inventory,
196
 
            self._new_revision_id)
 
180
                            self._new_revision_id)
197
181
 
198
182
    def finish_inventory(self):
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
 
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
            )
223
194
 
224
195
    def _gen_revision_id(self):
225
196
        """Return new revision-id."""
262
233
        # _new_revision_id
263
234
        ie.revision = self._new_revision_id
264
235
 
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
 
 
280
236
    def _get_delta(self, ie, basis_inv, path):
281
237
        """Get a delta against the basis inventory for ie."""
282
238
        if ie.file_id not in basis_inv:
324
280
            raise AssertionError("recording deletes not activated.")
325
281
        delta = (path, None, file_id, None)
326
282
        self._basis_delta.append(delta)
327
 
        self._any_changes = True
328
283
        return delta
329
284
 
330
285
    def will_record_deletes(self):
335
290
        builder.record_delete().
336
291
        """
337
292
        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
343
293
 
344
294
    def record_entry_contents(self, ie, parent_invs, path, tree,
345
295
        content_summary):
547
497
        else:
548
498
            raise NotImplementedError('unknown kind')
549
499
        ie.revision = self._new_revision_id
550
 
        self._any_changes = True
551
500
        return self._get_delta(ie, basis_inv, path), True, fingerprint
552
501
 
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
 
 
819
502
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
820
503
        # Note: as we read the content directly from the tree, we know its not
821
504
        # been turned into unicode or badly split - but a broken tree
844
527
        :param tree: The tree that is being committed.
845
528
        """
846
529
 
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
 
 
857
530
 
858
531
######################################################################
859
532
# Repositories
1211
884
 
1212
885
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
1213
886
        """
1214
 
        locked = self.is_locked()
1215
887
        result = self.control_files.lock_write(token=token)
1216
888
        for repo in self._fallback_repositories:
1217
889
            # Writes don't affect fallback repos
1218
890
            repo.lock_read()
1219
 
        if not locked:
1220
 
            self._refresh_data()
 
891
        self._refresh_data()
1221
892
        return result
1222
893
 
1223
894
    def lock_read(self):
1224
 
        locked = self.is_locked()
1225
895
        self.control_files.lock_read()
1226
896
        for repo in self._fallback_repositories:
1227
897
            repo.lock_read()
1228
 
        if not locked:
1229
 
            self._refresh_data()
 
898
        self._refresh_data()
1230
899
 
1231
900
    def get_physical_lock_status(self):
1232
901
        return self.control_files.get_physical_lock_status()
1354
1023
        return InterRepository.get(other, self).search_missing_revision_ids(
1355
1024
            revision_id, find_ghosts)
1356
1025
 
 
1026
    @deprecated_method(one_two)
 
1027
    @needs_read_lock
 
1028
    def missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
1029
        """Return the revision ids that other has that this does not.
 
1030
 
 
1031
        These are returned in topological order.
 
1032
 
 
1033
        revision_id: only return revision ids included by revision_id.
 
1034
        """
 
1035
        keys =  self.search_missing_revision_ids(
 
1036
            other, revision_id, find_ghosts).get_keys()
 
1037
        other.lock_read()
 
1038
        try:
 
1039
            parents = other.get_graph().get_parent_map(keys)
 
1040
        finally:
 
1041
            other.unlock()
 
1042
        return tsort.topo_sort(parents)
 
1043
 
1357
1044
    @staticmethod
1358
1045
    def open(base):
1359
1046
        """Open the repository rooted at base.
1397
1084
    def suspend_write_group(self):
1398
1085
        raise errors.UnsuspendableWriteGroup(self)
1399
1086
 
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
 
 
1413
1087
    def resume_write_group(self, tokens):
1414
1088
        if not self.is_write_locked():
1415
1089
            raise errors.NotWriteLocked(self)
1422
1096
    def _resume_write_group(self, tokens):
1423
1097
        raise errors.UnsuspendableWriteGroup(self)
1424
1098
 
1425
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1426
 
            fetch_spec=None):
 
1099
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
1427
1100
        """Fetch the content required to construct revision_id from source.
1428
1101
 
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
 
 
 
1102
        If revision_id is None all content is copied.
1436
1103
        :param find_ghosts: Find and copy revisions in the source that are
1437
1104
            ghosts in the target (and not reachable directly by walking out to
1438
1105
            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.
1446
1106
        """
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.")
1453
1107
        # fast path same-url fetch operations
1454
 
        if self.has_same_location(source) and fetch_spec is None:
 
1108
        if self.has_same_location(source):
1455
1109
            # check that last_revision is in 'from' and then return a
1456
1110
            # no-operation.
1457
1111
            if (revision_id is not None and
1463
1117
        # IncompatibleRepositories when asked to fetch.
1464
1118
        inter = InterRepository.get(source, self)
1465
1119
        return inter.fetch(revision_id=revision_id, pb=pb,
1466
 
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
 
1120
            find_ghosts=find_ghosts)
1467
1121
 
1468
1122
    def create_bundle(self, target, base, fileobj, format=None):
1469
1123
        return serializer.write_bundle(self, target, base, fileobj, format)
1639
1293
        # TODO: jam 20070210 This shouldn't be necessary since get_revision
1640
1294
        #       would have already do it.
1641
1295
        # 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.
1647
1296
        rev = self.get_revision(revision_id)
1648
1297
        rev_tmp = cStringIO.StringIO()
1649
1298
        # the current serializer..
1651
1300
        rev_tmp.seek(0)
1652
1301
        return rev_tmp.getvalue()
1653
1302
 
1654
 
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
 
1303
    def get_deltas_for_revisions(self, revisions):
1655
1304
        """Produce a generator of revision deltas.
1656
1305
 
1657
1306
        Note that the input is a sequence of REVISIONS, not revision_ids.
1658
1307
        Trees will be held in memory until the generator exits.
1659
1308
        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.
1664
1309
        """
1665
 
        # Get the revision-ids of interest
1666
1310
        required_trees = set()
1667
1311
        for revision in revisions:
1668
1312
            required_trees.add(revision.revision_id)
1669
1313
            required_trees.update(revision.parent_ids[:1])
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
 
1314
        trees = dict((t.get_revision_id(), t) for
 
1315
                     t in self.revision_trees(required_trees))
1686
1316
        for revision in revisions:
1687
1317
            if not revision.parent_ids:
1688
1318
                old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
1691
1321
            yield trees[revision.revision_id].changes_from(old_tree)
1692
1322
 
1693
1323
    @needs_read_lock
1694
 
    def get_revision_delta(self, revision_id, specific_fileids=None):
 
1324
    def get_revision_delta(self, revision_id):
1695
1325
        """Return the delta for one revision.
1696
1326
 
1697
1327
        The delta is relative to the left-hand predecessor of the
1698
1328
        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.
1703
1329
        """
1704
1330
        r = self.get_revision(revision_id)
1705
 
        return list(self.get_deltas_for_revisions([r],
1706
 
            specific_fileids=specific_fileids))[0]
 
1331
        return list(self.get_deltas_for_revisions([r]))[0]
1707
1332
 
1708
1333
    @needs_write_lock
1709
1334
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1718
1343
    def find_text_key_references(self):
1719
1344
        """Find the text key references within the repository.
1720
1345
 
 
1346
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
 
1347
        revision_ids. Each altered file-ids has the exact revision_ids that
 
1348
        altered it listed explicitly.
1721
1349
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1722
1350
            to whether they were referred to by the inventory of the
1723
1351
            revision_id that they contain. The inventory texts from all present
1812
1440
                result[key] = True
1813
1441
        return result
1814
1442
 
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
 
 
1835
1443
    def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
1836
1444
        revision_ids):
1837
1445
        """Helper routine for fileids_altered_by_revision_ids.
1847
1455
        revision_ids. Each altered file-ids has the exact revision_ids that
1848
1456
        altered it listed explicitly.
1849
1457
        """
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
1860
1458
        result = {}
1861
1459
        setdefault = result.setdefault
1862
 
        for key in new_keys:
1863
 
            setdefault(key[0], set()).add(key[-1])
 
1460
        for key in \
 
1461
            self._find_text_key_references_from_xml_inventory_lines(
 
1462
                line_iterator).iterkeys():
 
1463
            # once data is all ensured-consistent; then this is
 
1464
            # if revision_id == version_id
 
1465
            if key[-1:] in revision_ids:
 
1466
                setdefault(key[0], set()).add(key[-1])
1864
1467
        return result
1865
1468
 
1866
1469
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
1909
1512
        for record in self.texts.get_record_stream(text_keys, 'unordered', True):
1910
1513
            if record.storage_kind == 'absent':
1911
1514
                raise errors.RevisionNotPresent(record.key, self)
1912
 
            yield text_keys[record.key], record.get_bytes_as('chunked')
 
1515
            yield text_keys[record.key], record.get_bytes_as('fulltext')
1913
1516
 
1914
1517
    def _generate_text_key_index(self, text_key_references=None,
1915
1518
        ancestors=None):
1964
1567
        batch_size = 10 # should be ~150MB on a 55K path tree
1965
1568
        batch_count = len(revision_order) / batch_size + 1
1966
1569
        processed_texts = 0
1967
 
        pb.update("Calculating text parents", processed_texts, text_count)
 
1570
        pb.update("Calculating text parents.", processed_texts, text_count)
1968
1571
        for offset in xrange(batch_count):
1969
1572
            to_query = revision_order[offset * batch_size:(offset + 1) *
1970
1573
                batch_size]
1974
1577
                revision_id = rev_tree.get_revision_id()
1975
1578
                parent_ids = ancestors[revision_id]
1976
1579
                for text_key in revision_keys[revision_id]:
1977
 
                    pb.update("Calculating text parents", processed_texts)
 
1580
                    pb.update("Calculating text parents.", processed_texts)
1978
1581
                    processed_texts += 1
1979
1582
                    candidate_parents = []
1980
1583
                    for parent_id in parent_ids:
2076
1679
        inventories in memory, but will only parse a single inventory at a
2077
1680
        time.
2078
1681
 
2079
 
        :param revision_ids: The expected revision ids of the inventories.
2080
1682
        :return: An iterator of inventories.
2081
1683
        """
2082
1684
        if ((None in revision_ids)
2198
1800
        for repositories to maintain loaded indices across multiple locks
2199
1801
        by checking inside their implementation of this method to see
2200
1802
        whether their indices are still valid. This depends of course on
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.
 
1803
        the disk format being validatable in this manner.
2206
1804
        """
2207
1805
 
2208
1806
    @needs_read_lock
2222
1820
            return RevisionTree(self, inv, revision_id)
2223
1821
 
2224
1822
    def revision_trees(self, revision_ids):
2225
 
        """Return Trees for revisions in this repository.
 
1823
        """Return Tree for a revision on this branch.
2226
1824
 
2227
 
        :param revision_ids: a sequence of revision-ids;
2228
 
          a revision-id may not be None or 'null:'
2229
 
        """
 
1825
        `revision_id` may not be None or 'null:'"""
2230
1826
        inventories = self.iter_inventories(revision_ids)
2231
1827
        for inv in inventories:
2232
1828
            yield RevisionTree(self, inv, inv.revision_id)
2233
1829
 
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
 
 
2250
1830
    @needs_read_lock
2251
1831
    def get_ancestry(self, revision_id, topo_sorted=True):
2252
1832
        """Return a list of revision-ids integrated by a revision.
2288
1868
        implicitly lock for the user.
2289
1869
        """
2290
1870
 
 
1871
    @needs_read_lock
 
1872
    @deprecated_method(one_six)
 
1873
    def print_file(self, file, revision_id):
 
1874
        """Print `file` to stdout.
 
1875
 
 
1876
        FIXME RBC 20060125 as John Meinel points out this is a bad api
 
1877
        - it writes to stdout, it assumes that that is valid etc. Fix
 
1878
        by creating a new more flexible convenience function.
 
1879
        """
 
1880
        tree = self.revision_tree(revision_id)
 
1881
        # use inventory as it was in that revision
 
1882
        file_id = tree.inventory.path2id(file)
 
1883
        if not file_id:
 
1884
            # TODO: jam 20060427 Write a test for this code path
 
1885
            #       it had a bug in it, and was raising the wrong
 
1886
            #       exception.
 
1887
            raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
 
1888
        tree.print_file(file_id)
 
1889
 
2291
1890
    def get_transaction(self):
2292
1891
        return self.control_files.get_transaction()
2293
1892
 
 
1893
    @deprecated_method(one_one)
 
1894
    def get_parents(self, revision_ids):
 
1895
        """See StackedParentsProvider.get_parents"""
 
1896
        parent_map = self.get_parent_map(revision_ids)
 
1897
        return [parent_map.get(r, None) for r in revision_ids]
 
1898
 
2294
1899
    def get_parent_map(self, revision_ids):
2295
1900
        """See graph._StackedParentsProvider.get_parent_map"""
2296
1901
        # revisions index works in keys; this just works in revisions
2325
1930
                [parents_provider, other_repository._make_parents_provider()])
2326
1931
        return graph.Graph(parents_provider)
2327
1932
 
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)
 
1933
    def _get_versioned_file_checker(self):
 
1934
        """Return an object suitable for checking versioned files."""
 
1935
        return _VersionedFileChecker(self)
2339
1936
 
2340
1937
    def revision_ids_to_search_result(self, result_set):
2341
1938
        """Convert a set of revision ids to a graph SearchResult."""
2643
2240
 
2644
2241
    Once a format is deprecated, just deprecate the initialize and open
2645
2242
    methods on the format class. Do not deprecate the object, as the
2646
 
    object may be created even when a repository instance hasn't been
 
2243
    object may be created even when a repository instnace hasn't been
2647
2244
    created.
2648
2245
 
2649
2246
    Common instance attributes:
2671
2268
    # Should fetch trigger a reconcile after the fetch? Only needed for
2672
2269
    # some repository formats that can suffer internal inconsistencies.
2673
2270
    _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
2677
2271
 
2678
2272
    def __str__(self):
2679
2273
        return "<%s>" % self.__class__.__name__
2845
2439
# Pre-0.8 formats that don't have a disk format string (because they are
2846
2440
# versioned by the matching control directory). We use the control directories
2847
2441
# disk format string as a key for the network_name because they meet the
2848
 
# constraints (simple string, unique, immutable).
 
2442
# constraints (simple string, unique, immmutable).
2849
2443
network_format_registry.register_lazy(
2850
2444
    "Bazaar-NG branch, format 5\n",
2851
2445
    'bzrlib.repofmt.weaverepo',
2958
2552
    InterRepository.get(other).method_name(parameters).
2959
2553
    """
2960
2554
 
2961
 
    _walk_to_common_revisions_batch_size = 50
 
2555
    _walk_to_common_revisions_batch_size = 1
2962
2556
    _optimisers = []
2963
2557
    """The available optimised InterRepository types."""
2964
2558
 
 
2559
    def __init__(self, source, target):
 
2560
        InterObject.__init__(self, source, target)
 
2561
        # These two attributes may be overridden by e.g. InterOtherToRemote to
 
2562
        # provide a faster implementation.
 
2563
        self.target_get_graph = self.target.get_graph
 
2564
        self.target_get_parent_map = self.target.get_parent_map
 
2565
 
2965
2566
    @needs_write_lock
2966
2567
    def copy_content(self, revision_id=None):
2967
2568
        """Make a complete copy of the content in self into destination.
2978
2579
            pass
2979
2580
        self.target.fetch(self.source, revision_id=revision_id)
2980
2581
 
2981
 
    @needs_write_lock
2982
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
2983
 
            fetch_spec=None):
 
2582
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2984
2583
        """Fetch the content required to construct revision_id.
2985
2584
 
2986
2585
        The content is copied from self.source to self.target.
2992
2591
        :return: None.
2993
2592
        """
2994
2593
        from bzrlib.fetch import RepoFetcher
 
2594
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2595
               self.source, self.source._format, self.target,
 
2596
               self.target._format)
2995
2597
        f = RepoFetcher(to_repository=self.target,
2996
2598
                               from_repository=self.source,
2997
2599
                               last_revision=revision_id,
2998
 
                               fetch_spec=fetch_spec,
2999
2600
                               pb=pb, find_ghosts=find_ghosts)
3000
2601
 
3001
2602
    def _walk_to_common_revisions(self, revision_ids):
3004
2605
        :param revision_ids: The start point for the search.
3005
2606
        :return: A set of revision ids.
3006
2607
        """
3007
 
        target_graph = self.target.get_graph()
 
2608
        target_graph = self.target_get_graph()
3008
2609
        revision_ids = frozenset(revision_ids)
3009
2610
        # Fast path for the case where all the revisions are already in the
3010
2611
        # target repo.
3057
2658
                break
3058
2659
        return searcher.get_result()
3059
2660
 
 
2661
    @deprecated_method(one_two)
 
2662
    @needs_read_lock
 
2663
    def missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
2664
        """Return the revision ids that source has that target does not.
 
2665
 
 
2666
        These are returned in topological order.
 
2667
 
 
2668
        :param revision_id: only return revision ids included by this
 
2669
                            revision_id.
 
2670
        :param find_ghosts: If True find missing revisions in deep history
 
2671
            rather than just finding the surface difference.
 
2672
        """
 
2673
        return list(self.search_missing_revision_ids(
 
2674
            revision_id, find_ghosts).get_keys())
 
2675
 
3060
2676
    @needs_read_lock
3061
2677
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3062
2678
        """Return the revision ids that source has that target does not.
3195
2811
        else:
3196
2812
            self.target.fetch(self.source, revision_id=revision_id)
3197
2813
 
 
2814
    @needs_write_lock
 
2815
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2816
        """See InterRepository.fetch()."""
 
2817
        from bzrlib.fetch import RepoFetcher
 
2818
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2819
               self.source, self.source._format, self.target, self.target._format)
 
2820
        f = RepoFetcher(to_repository=self.target,
 
2821
                               from_repository=self.source,
 
2822
                               last_revision=revision_id,
 
2823
                               pb=pb, find_ghosts=find_ghosts)
 
2824
 
3198
2825
    @needs_read_lock
3199
2826
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3200
2827
        """See InterRepository.missing_revision_ids()."""
3205
2832
        # so the first thing is to get a subset of the revisions to
3206
2833
        # satisfy revision_id in source, and then eliminate those that
3207
2834
        # we do already have.
3208
 
        # this is slow on high latency connection to self, but as this
 
2835
        # this is slow on high latency connection to self, but as as this
3209
2836
        # disk format scales terribly for push anyway due to rewriting
3210
2837
        # inventory.weave, this is considered acceptable.
3211
2838
        # - RBC 20060209
3264
2891
            return False
3265
2892
        return are_knits and InterRepository._same_model(source, target)
3266
2893
 
 
2894
    @needs_write_lock
 
2895
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2896
        """See InterRepository.fetch()."""
 
2897
        from bzrlib.fetch import RepoFetcher
 
2898
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2899
               self.source, self.source._format, self.target, self.target._format)
 
2900
        f = RepoFetcher(to_repository=self.target,
 
2901
                            from_repository=self.source,
 
2902
                            last_revision=revision_id,
 
2903
                            pb=pb, find_ghosts=find_ghosts)
 
2904
 
3267
2905
    @needs_read_lock
3268
2906
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3269
2907
        """See InterRepository.missing_revision_ids()."""
3323
2961
        return are_packs and InterRepository._same_model(source, target)
3324
2962
 
3325
2963
    @needs_write_lock
3326
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3327
 
            fetch_spec=None):
 
2964
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3328
2965
        """See InterRepository.fetch()."""
3329
2966
        if (len(self.source._fallback_repositories) > 0 or
3330
2967
            len(self.target._fallback_repositories) > 0):
3334
2971
            # attributes on repository.
3335
2972
            from bzrlib.fetch import RepoFetcher
3336
2973
            fetcher = RepoFetcher(self.target, self.source, revision_id,
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
 
2974
                                  pb, find_ghosts)
 
2975
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2976
               self.source, self.source._format, self.target, self.target._format)
3345
2977
        if revision_id is None:
3346
2978
            # TODO:
3347
2979
            # everything to do - use pack logic
3350
2982
            # till then:
3351
2983
            source_revision_ids = frozenset(self.source.all_revision_ids())
3352
2984
            revision_ids = source_revision_ids - \
3353
 
                frozenset(self.target.get_parent_map(source_revision_ids))
 
2985
                frozenset(self.target_get_parent_map(source_revision_ids))
3354
2986
            revision_keys = [(revid,) for revid in revision_ids]
3355
 
            index = self.target._pack_collection.revision_index.combined_index
 
2987
            target_pack_collection = self._get_target_pack_collection()
 
2988
            index = target_pack_collection.revision_index.combined_index
3356
2989
            present_revision_ids = set(item[1][0] for item in
3357
2990
                index.iter_entries(revision_keys))
3358
2991
            revision_ids = set(revision_ids) - present_revision_ids
3378
3011
 
3379
3012
    def _pack(self, source, target, revision_ids):
3380
3013
        from bzrlib.repofmt.pack_repo import Packer
 
3014
        target_pack_collection = self._get_target_pack_collection()
3381
3015
        packs = source._pack_collection.all_packs()
3382
 
        pack = Packer(self.target._pack_collection, packs, '.fetch',
 
3016
        pack = Packer(target_pack_collection, packs, '.fetch',
3383
3017
            revision_ids).pack()
3384
3018
        if pack is not None:
3385
 
            self.target._pack_collection._save_pack_names()
 
3019
            target_pack_collection._save_pack_names()
3386
3020
            copied_revs = pack.get_revision_count()
3387
3021
            # Trigger an autopack. This may duplicate effort as we've just done
3388
3022
            # a pack creation, but for now it is simpler to think about as
3389
3023
            # 'upload data, then repack if needed'.
3390
 
            self.target._pack_collection.autopack()
 
3024
            self._autopack()
3391
3025
            return (copied_revs, [])
3392
3026
        else:
3393
3027
            return (0, [])
3394
3028
 
 
3029
    def _autopack(self):
 
3030
        self.target._pack_collection.autopack()
 
3031
 
 
3032
    def _get_target_pack_collection(self):
 
3033
        return self.target._pack_collection
 
3034
 
3395
3035
    @needs_read_lock
3396
3036
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3397
3037
        """See InterRepository.missing_revision_ids().
3404
3044
        elif revision_id is not None:
3405
3045
            # Find ghosts: search for revisions pointing from one repository to
3406
3046
            # the other, and vice versa, anywhere in the history of revision_id.
3407
 
            graph = self.target.get_graph(other_repository=self.source)
 
3047
            graph = self.target_get_graph(other_repository=self.source)
3408
3048
            searcher = graph._make_breadth_first_searcher([revision_id])
3409
3049
            found_ids = set()
3410
3050
            while True:
3420
3060
            # Double query here: should be able to avoid this by changing the
3421
3061
            # graph api further.
3422
3062
            result_set = found_ids - frozenset(
3423
 
                self.target.get_parent_map(found_ids))
 
3063
                self.target_get_parent_map(found_ids))
3424
3064
        else:
3425
3065
            source_ids = self.source.all_revision_ids()
3426
3066
            # source_ids is the worst possible case we may need to pull.
3498
3138
                        # We don't copy the text for the root node unless the
3499
3139
                        # target supports_rich_root.
3500
3140
                        continue
3501
 
                    text_keys.add((file_id, entry.revision))
 
3141
                    # TODO: Do we need:
 
3142
                    #       "if entry.revision == current_revision_id" ?
 
3143
                    if entry.revision == current_revision_id:
 
3144
                        text_keys.add((file_id, entry.revision))
3502
3145
            revision = self.source.get_revision(current_revision_id)
3503
3146
            pending_deltas.append((basis_id, delta,
3504
3147
                current_revision_id, revision.parent_ids))
3555
3198
                  len(revision_ids))
3556
3199
 
3557
3200
    @needs_write_lock
3558
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3559
 
            fetch_spec=None):
 
3201
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3560
3202
        """See InterRepository.fetch()."""
3561
 
        if fetch_spec is not None:
3562
 
            raise AssertionError("Not implemented yet...")
3563
3203
        revision_ids = self.target.search_missing_revision_ids(self.source,
3564
3204
            revision_id, find_ghosts=find_ghosts).get_keys()
3565
3205
        if not revision_ids:
3570
3210
            my_pb = ui.ui_factory.nested_progress_bar()
3571
3211
            pb = my_pb
3572
3212
        else:
3573
 
            symbol_versioning.warn(
3574
 
                symbol_versioning.deprecated_in((1, 14, 0))
3575
 
                % "pb parameter to fetch()")
3576
3213
            my_pb = None
3577
3214
        try:
3578
3215
            self._fetch_all_revisions(revision_ids, pb)
3604
3241
        return basis_id, basis_tree
3605
3242
 
3606
3243
 
 
3244
class InterOtherToRemote(InterRepository):
 
3245
    """An InterRepository that simply delegates to the 'real' InterRepository
 
3246
    calculated for (source, target._real_repository).
 
3247
    """
 
3248
 
 
3249
    _walk_to_common_revisions_batch_size = 50
 
3250
 
 
3251
    def __init__(self, source, target):
 
3252
        InterRepository.__init__(self, source, target)
 
3253
        self._real_inter = None
 
3254
 
 
3255
    @staticmethod
 
3256
    def is_compatible(source, target):
 
3257
        if isinstance(target, remote.RemoteRepository):
 
3258
            return True
 
3259
        return False
 
3260
 
 
3261
    def _ensure_real_inter(self):
 
3262
        if self._real_inter is None:
 
3263
            self.target._ensure_real()
 
3264
            real_target = self.target._real_repository
 
3265
            self._real_inter = InterRepository.get(self.source, real_target)
 
3266
            # Make _real_inter use the RemoteRepository for get_parent_map
 
3267
            self._real_inter.target_get_graph = self.target.get_graph
 
3268
            self._real_inter.target_get_parent_map = self.target.get_parent_map
 
3269
 
 
3270
    def copy_content(self, revision_id=None):
 
3271
        self._ensure_real_inter()
 
3272
        self._real_inter.copy_content(revision_id=revision_id)
 
3273
 
 
3274
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
3275
        self._ensure_real_inter()
 
3276
        return self._real_inter.fetch(revision_id=revision_id, pb=pb,
 
3277
            find_ghosts=find_ghosts)
 
3278
 
 
3279
    @classmethod
 
3280
    def _get_repo_format_to_test(self):
 
3281
        return None
 
3282
 
 
3283
 
 
3284
class InterRemoteToOther(InterRepository):
 
3285
 
 
3286
    def __init__(self, source, target):
 
3287
        InterRepository.__init__(self, source, target)
 
3288
        self._real_inter = None
 
3289
 
 
3290
    @staticmethod
 
3291
    def is_compatible(source, target):
 
3292
        if not isinstance(source, remote.RemoteRepository):
 
3293
            return False
 
3294
        return InterRepository._same_model(source, target)
 
3295
 
 
3296
    def _ensure_real_inter(self):
 
3297
        if self._real_inter is None:
 
3298
            self.source._ensure_real()
 
3299
            real_source = self.source._real_repository
 
3300
            self._real_inter = InterRepository.get(real_source, self.target)
 
3301
 
 
3302
    @needs_write_lock
 
3303
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
3304
        """See InterRepository.fetch()."""
 
3305
        # Always fetch using the generic streaming fetch code, to allow
 
3306
        # streaming fetching from remote servers.
 
3307
        from bzrlib.fetch import RepoFetcher
 
3308
        fetcher = RepoFetcher(self.target, self.source, revision_id,
 
3309
                              pb, find_ghosts)
 
3310
 
 
3311
    def copy_content(self, revision_id=None):
 
3312
        self._ensure_real_inter()
 
3313
        self._real_inter.copy_content(revision_id=revision_id)
 
3314
 
 
3315
    @classmethod
 
3316
    def _get_repo_format_to_test(self):
 
3317
        return None
 
3318
 
 
3319
 
 
3320
 
 
3321
class InterPackToRemotePack(InterPackRepo):
 
3322
    """A specialisation of InterPackRepo for a target that is a
 
3323
    RemoteRepository.
 
3324
 
 
3325
    This will use the get_parent_map RPC rather than plain readvs, and also
 
3326
    uses an RPC for autopacking.
 
3327
    """
 
3328
 
 
3329
    _walk_to_common_revisions_batch_size = 50
 
3330
 
 
3331
    @staticmethod
 
3332
    def is_compatible(source, target):
 
3333
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
 
3334
        if isinstance(source._format, RepositoryFormatPack):
 
3335
            if isinstance(target, remote.RemoteRepository):
 
3336
                target._format._ensure_real()
 
3337
                if isinstance(target._format._custom_format,
 
3338
                              RepositoryFormatPack):
 
3339
                    if InterRepository._same_model(source, target):
 
3340
                        return True
 
3341
        return False
 
3342
 
 
3343
    def _autopack(self):
 
3344
        self.target.autopack()
 
3345
 
 
3346
    @needs_write_lock
 
3347
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
3348
        """See InterRepository.fetch()."""
 
3349
        # Always fetch using the generic streaming fetch code, to allow
 
3350
        # streaming fetching into remote servers.
 
3351
        from bzrlib.fetch import RepoFetcher
 
3352
        fetcher = RepoFetcher(self.target, self.source, revision_id,
 
3353
                              pb, find_ghosts)
 
3354
 
 
3355
    def _get_target_pack_collection(self):
 
3356
        return self.target._real_repository._pack_collection
 
3357
 
 
3358
    @classmethod
 
3359
    def _get_repo_format_to_test(self):
 
3360
        return None
 
3361
 
 
3362
 
3607
3363
InterRepository.register_optimiser(InterDifferingSerializer)
3608
3364
InterRepository.register_optimiser(InterSameDataRepository)
3609
3365
InterRepository.register_optimiser(InterWeaveRepo)
3610
3366
InterRepository.register_optimiser(InterKnitRepo)
3611
3367
InterRepository.register_optimiser(InterPackRepo)
 
3368
InterRepository.register_optimiser(InterOtherToRemote)
 
3369
InterRepository.register_optimiser(InterRemoteToOther)
 
3370
InterRepository.register_optimiser(InterPackToRemotePack)
3612
3371
 
3613
3372
 
3614
3373
class CopyConverter(object):
3695
3454
 
3696
3455
class _VersionedFileChecker(object):
3697
3456
 
3698
 
    def __init__(self, repository, text_key_references=None):
 
3457
    def __init__(self, repository):
3699
3458
        self.repository = repository
3700
 
        self.text_index = self.repository._generate_text_key_index(
3701
 
            text_key_references=text_key_references)
 
3459
        self.text_index = self.repository._generate_text_key_index()
3702
3460
 
3703
3461
    def calculate_file_version_parents(self, text_key):
3704
3462
        """Calculate the correct parents for a file version according to
3806
3564
    def _locked_insert_stream(self, stream, src_format):
3807
3565
        to_serializer = self.target_repo._format._serializer
3808
3566
        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)
3827
3567
        for substream_type, substream in stream:
3828
3568
            if substream_type == 'texts':
3829
3569
                self.target_repo.texts.insert_record_stream(substream)