[MERGE] Refactor fetch: add get_data_about_revision_ids to repository, and other changes.

Andrew Bennetts andrew at puzzling.org
Wed Aug 8 07:24:28 BST 2007


Andrew Bennetts wrote:
> 
> I've addressed all the review comments, but here's a new bundle.  The part I
> want feedback on is the new method name, and its description:
> item_keys_introduced_by(revision_ids).

Ahem.  And here's a *current* bundle, after actually committing the relevant
changes.  Sorry for the noise!

-Andrew.

-------------- next part --------------
# Bazaar merge directive format 2 (Bazaar 0.19)
# revision_id: andrew.bennetts at canonical.com-20070808062316-\
#   1baiqpdwfzvznju1
# target_branch: http://bazaar-vcs.org/bzr/bzr.dev
# testament_sha1: 5b7093f7324cb9ed0451a2c569db46fd4e8967de
# timestamp: 2007-08-08 16:23:25 +1000
# source_branch: http://people.ubuntu.com/~andrew/bzr/fetch-refactor
# base_revision_id: andrew.bennetts at canonical.com-20070807074727-\
#   5vmpk09r98lyef00
# 
# Begin patch
=== modified file 'bzrlib/fetch.py'
--- bzrlib/fetch.py	2007-06-22 22:19:13 +0000
+++ bzrlib/fetch.py	2007-08-08 06:23:16 +0000
@@ -36,7 +36,7 @@
 from bzrlib.errors import (InstallFailed,
                            )
 from bzrlib.progress import ProgressPhase
-from bzrlib.revision import NULL_REVISION
+from bzrlib.revision import is_null, NULL_REVISION
 from bzrlib.symbol_versioning import (deprecated_function,
         deprecated_method,
         zero_eight,
@@ -89,9 +89,10 @@
         # result variables.
         self.failed_revisions = []
         self.count_copied = 0
-        if to_repository.control_files._transport.base == from_repository.control_files._transport.base:
-            # check that last_revision is in 'from' and then return a no-operation.
-            if last_revision not in (None, NULL_REVISION):
+        if to_repository.has_same_location(from_repository):
+            # check that last_revision is in 'from' and then return a
+            # no-operation.
+            if last_revision is not None and not is_null(last_revision):
                 to_repository.get_revision(last_revision)
             return
         self.to_repository = to_repository
@@ -132,19 +133,63 @@
         try:
             pp.next_phase()
             revs = self._revids_to_fetch()
-            # something to do ?
-            if revs:
-                pp.next_phase()
-                self._fetch_weave_texts(revs)
-                pp.next_phase()
-                self._fetch_inventory_weave(revs)
-                pp.next_phase()
-                self._fetch_revision_texts(revs)
-                self.count_copied += len(revs)
+            self._fetch_everything_for_revisions(revs, pp)
         finally:
             self.pb.clear()
 
+    def _fetch_everything_for_revisions(self, revs, pp):
+        """Fetch all data for the given set of revisions."""
+        if revs is None:
+            return
+        # The first phase is "file".  We pass the progress bar for it directly
+        # into item_keys_introduced_by, which has more information about how
+        # that phase is progressing than we do.  Progress updates for the other
+        # phases are taken care of in this function.
+        # XXX: there should be a clear owner of the progress reporting.  Perhaps
+        # item_keys_introduced_by should have a richer API than it does at the
+        # moment, so that it can feed the progress information back to this
+        # function?
+        phase = 'file'
+        pb = bzrlib.ui.ui_factory.nested_progress_bar()
+        try:
+            data_to_fetch = self.from_repository.item_keys_introduced_by(revs, pb)
+            for knit_kind, file_id, revisions in data_to_fetch:
+                if knit_kind != phase:
+                    phase = knit_kind
+                    # Make a new progress bar for this phase
+                    pb.finished()
+                    pp.next_phase()
+                    pb = bzrlib.ui.ui_factory.nested_progress_bar()
+                if knit_kind == "file":
+                    self._fetch_weave_text(file_id, revisions)
+                elif knit_kind == "inventory":
+                    # XXX:
+                    # Once we've processed all the files, then we generate the root
+                    # texts (if necessary), then we process the inventory.  It's a
+                    # bit distasteful to have knit_kind == "inventory" mean this,
+                    # perhaps it should happen on the first non-"file" knit, in case
+                    # it's not always inventory?
+                    self._generate_root_texts(revs)
+                    self._fetch_inventory_weave(revs, pb)
+                elif knit_kind == "signatures":
+                    # Nothing to do here; this will be taken care of when
+                    # _fetch_revision_texts happens.
+                    pass
+                elif knit_kind == "revisions":
+                    self._fetch_revision_texts(revs, pb)
+                else:
+                    raise AssertionError("Unknown knit kind %r" % knit_kind)
+        finally:
+            if pb is not None:
+                pb.finished()
+        self.count_copied += len(revs)
+        
     def _revids_to_fetch(self):
+        """Determines the exact revisions needed from self.from_repository to
+        install self._last_revision in self.to_repository.
+
+        If no revisions need to be fetched, then this just returns None.
+        """
         mutter('fetch up to rev {%s}', self._last_revision)
         if self._last_revision is NULL_REVISION:
             # explicit limit of no revisions needed
@@ -159,65 +204,55 @@
         except errors.NoSuchRevision:
             raise InstallFailed([self._last_revision])
 
-    def _fetch_weave_texts(self, revs):
-        texts_pb = bzrlib.ui.ui_factory.nested_progress_bar()
-        try:
-            # fileids_altered_by_revision_ids requires reading the inventory
-            # weave, we will need to read the inventory weave again when
-            # all this is done, so enable caching for that specific weave
-            inv_w = self.from_repository.get_inventory_weave()
-            inv_w.enable_cache()
-            file_ids = self.from_repository.fileids_altered_by_revision_ids(revs)
-            count = 0
-            num_file_ids = len(file_ids)
-            for file_id, required_versions in file_ids.items():
-                texts_pb.update("fetch texts", count, num_file_ids)
-                count +=1
-                to_weave = self.to_weaves.get_weave_or_empty(file_id,
-                    self.to_repository.get_transaction())
-                from_weave = self.from_weaves.get_weave(file_id,
-                    self.from_repository.get_transaction())
-                # we fetch all the texts, because texts do
-                # not reference anything, and its cheap enough
-                to_weave.join(from_weave, version_ids=required_versions)
-                # we don't need *all* of this data anymore, but we dont know
-                # what we do. This cache clearing will result in a new read 
-                # of the knit data when we do the checkout, but probably we
-                # want to emit the needed data on the fly rather than at the
-                # end anyhow.
-                # the from weave should know not to cache data being joined,
-                # but its ok to ask it to clear.
-                from_weave.clear_cache()
-                to_weave.clear_cache()
-        finally:
-            texts_pb.finished()
-
-    def _fetch_inventory_weave(self, revs):
-        pb = bzrlib.ui.ui_factory.nested_progress_bar()
-        try:
-            pb.update("fetch inventory", 0, 2)
-            to_weave = self.to_control.get_weave('inventory',
-                    self.to_repository.get_transaction())
-    
-            child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
-            try:
-                # just merge, this is optimisable and its means we don't
-                # copy unreferenced data such as not-needed inventories.
-                pb.update("fetch inventory", 1, 3)
-                from_weave = self.from_repository.get_inventory_weave()
-                pb.update("fetch inventory", 2, 3)
-                # we fetch only the referenced inventories because we do not
-                # know for unselected inventories whether all their required
-                # texts are present in the other repository - it could be
-                # corrupt.
-                to_weave.join(from_weave, pb=child_pb, msg='merge inventory',
-                              version_ids=revs)
-                from_weave.clear_cache()
-            finally:
-                child_pb.finished()
-        finally:
-            pb.finished()
-
+    def _fetch_weave_text(self, file_id, required_versions):
+        to_weave = self.to_weaves.get_weave_or_empty(file_id,
+            self.to_repository.get_transaction())
+        from_weave = self.from_weaves.get_weave(file_id,
+            self.from_repository.get_transaction())
+        # we fetch all the texts, because texts do
+        # not reference anything, and its cheap enough
+        to_weave.join(from_weave, version_ids=required_versions)
+        # we don't need *all* of this data anymore, but we dont know
+        # what we do. This cache clearing will result in a new read 
+        # of the knit data when we do the checkout, but probably we
+        # want to emit the needed data on the fly rather than at the
+        # end anyhow.
+        # the from weave should know not to cache data being joined,
+        # but its ok to ask it to clear.
+        from_weave.clear_cache()
+        to_weave.clear_cache()
+
+    def _fetch_inventory_weave(self, revs, pb):
+        pb.update("fetch inventory", 0, 2)
+        to_weave = self.to_control.get_weave('inventory',
+                self.to_repository.get_transaction())
+
+        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
+        try:
+            # just merge, this is optimisable and its means we don't
+            # copy unreferenced data such as not-needed inventories.
+            pb.update("fetch inventory", 1, 3)
+            from_weave = self.from_repository.get_inventory_weave()
+            pb.update("fetch inventory", 2, 3)
+            # we fetch only the referenced inventories because we do not
+            # know for unselected inventories whether all their required
+            # texts are present in the other repository - it could be
+            # corrupt.
+            to_weave.join(from_weave, pb=child_pb, msg='merge inventory',
+                          version_ids=revs)
+            from_weave.clear_cache()
+        finally:
+            child_pb.finished()
+
+    def _generate_root_texts(self, revs):
+        """This will be called by __fetch between fetching weave texts and
+        fetching the inventory weave.
+
+        Subclasses should override this if they need to generate root texts
+        after fetching weave texts.
+        """
+        pass
+        
 
 class GenericRepoFetcher(RepoFetcher):
     """This is a generic repo to repo fetcher.
@@ -226,37 +261,29 @@
     It triggers a reconciliation after fetching to ensure integrity.
     """
 
-    def _fetch_revision_texts(self, revs):
+    def _fetch_revision_texts(self, revs, pb):
         """Fetch revision object texts"""
-        rev_pb = bzrlib.ui.ui_factory.nested_progress_bar()
-        try:
-            to_txn = self.to_transaction = self.to_repository.get_transaction()
-            count = 0
-            total = len(revs)
-            to_store = self.to_repository._revision_store
-            for rev in revs:
-                pb = bzrlib.ui.ui_factory.nested_progress_bar()
-                try:
-                    pb.update('copying revisions', count, total)
-                    try:
-                        sig_text = self.from_repository.get_signature_text(rev)
-                        to_store.add_revision_signature_text(rev, sig_text, to_txn)
-                    except errors.NoSuchRevision:
-                        # not signed.
-                        pass
-                    to_store.add_revision(self.from_repository.get_revision(rev),
-                                          to_txn)
-                    count += 1
-                finally:
-                    pb.finished()
-            # fixup inventory if needed: 
-            # this is expensive because we have no inverse index to current ghosts.
-            # but on local disk its a few seconds and sftp push is already insane.
-            # so we just-do-it.
-            # FIXME: repository should inform if this is needed.
-            self.to_repository.reconcile()
-        finally:
-            rev_pb.finished()
+        to_txn = self.to_transaction = self.to_repository.get_transaction()
+        count = 0
+        total = len(revs)
+        to_store = self.to_repository._revision_store
+        for rev in revs:
+            pb.update('copying revisions', count, total)
+            try:
+                sig_text = self.from_repository.get_signature_text(rev)
+                to_store.add_revision_signature_text(rev, sig_text, to_txn)
+            except errors.NoSuchRevision:
+                # not signed.
+                pass
+            to_store.add_revision(self.from_repository.get_revision(rev),
+                                  to_txn)
+            count += 1
+        # fixup inventory if needed: 
+        # this is expensive because we have no inverse index to current ghosts.
+        # but on local disk its a few seconds and sftp push is already insane.
+        # so we just-do-it.
+        # FIXME: repository should inform if this is needed.
+        self.to_repository.reconcile()
     
 
 class KnitRepoFetcher(RepoFetcher):
@@ -267,7 +294,7 @@
     copy revision texts.
     """
 
-    def _fetch_revision_texts(self, revs):
+    def _fetch_revision_texts(self, revs, pb):
         # may need to be a InterRevisionStore call here.
         from_transaction = self.from_repository.get_transaction()
         to_transaction = self.to_repository.get_transaction()
@@ -357,12 +384,10 @@
         GenericRepoFetcher.__init__(self, to_repository, from_repository,
                                     last_revision, pb)
 
-    def _fetch_weave_texts(self, revs):
-        GenericRepoFetcher._fetch_weave_texts(self, revs)
-        # Now generate a weave for the tree root
+    def _generate_root_texts(self, revs):
         self.helper.generate_root_texts(revs)
 
-    def _fetch_inventory_weave(self, revs):
+    def _fetch_inventory_weave(self, revs, pb):
         self.helper.regenerate_inventory(revs)
  
 
@@ -375,12 +400,10 @@
         KnitRepoFetcher.__init__(self, to_repository, from_repository,
                                  last_revision, pb)
 
-    def _fetch_weave_texts(self, revs):
-        KnitRepoFetcher._fetch_weave_texts(self, revs)
-        # Now generate a weave for the tree root
+    def _generate_root_texts(self, revs):
         self.helper.generate_root_texts(revs)
 
-    def _fetch_inventory_weave(self, revs):
+    def _fetch_inventory_weave(self, revs, pb):
         self.helper.regenerate_inventory(revs)
         
 

=== modified file 'bzrlib/repository.py'
--- bzrlib/repository.py	2007-08-07 07:04:58 +0000
+++ bzrlib/repository.py	2007-08-08 06:23:16 +0000
@@ -636,6 +636,57 @@
             pb.finished()
         return result
 
+    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
+        """Get an iterable listing the keys of all the data introduced by a set
+        of revision IDs.
+
+        The keys will be ordered so that the corresponding items can be safely
+        fetched and inserted in that order.
+
+        :returns: An iterable producing tuples of (knit-kind, file-id,
+            versions).  knit-kind is one of 'file', 'inventory', 'signatures',
+            'revisions'.  file-id is None unless knit-kind is 'file'.
+        """
+        # XXX: it's a bit weird to control the inventory weave caching in this
+        # generator.  Ideally the caching would be done in fetch.py I think.  Or
+        # maybe this generator should explicitly have the contract that it
+        # should not be iterated until the previously yielded item has been
+        # processed?
+        inv_w = self.get_inventory_weave()
+        inv_w.enable_cache()
+
+        # file ids that changed
+        file_ids = self.fileids_altered_by_revision_ids(revision_ids)
+        count = 0
+        num_file_ids = len(file_ids)
+        for file_id, altered_versions in file_ids.iteritems():
+            if _files_pb is not None:
+                _files_pb.update("fetch texts", count, num_file_ids)
+            count += 1
+            yield ("file", file_id, altered_versions)
+        # We're done with the files_pb.  Note that it finished by the caller,
+        # just as it was created by the caller.
+        del _files_pb
+
+        # inventory
+        yield ("inventory", None, revision_ids)
+        inv_w.clear_cache()
+
+        # signatures
+        revisions_with_signatures = set()
+        for rev_id in revision_ids:
+            try:
+                self.get_signature_text(rev_id)
+            except errors.NoSuchRevision:
+                # not signed.
+                pass
+            else:
+                revisions_with_signatures.add(rev_id)
+        yield ("signatures", None, revisions_with_signatures)
+
+        # revisions
+        yield ("revisions", None, revision_ids)
+
     @needs_read_lock
     def get_inventory_weave(self):
         return self.control_weaves.get_weave('inventory',

=== modified file 'bzrlib/tests/repository_implementations/test_repository.py'
--- bzrlib/tests/repository_implementations/test_repository.py	2007-08-07 07:47:27 +0000
+++ bzrlib/tests/repository_implementations/test_repository.py	2007-08-08 00:40:14 +0000
@@ -210,6 +210,21 @@
         rev2_tree = knit3_repo.revision_tree('rev2')
         self.assertEqual('rev1', rev2_tree.inventory.root.revision)
 
+    def makeARepoWithSignatures(self):
+        wt = self.make_branch_and_tree('a-repo-with-sigs')
+        wt.commit('rev1', allow_pointless=True, rev_id='rev1')
+        repo = wt.branch.repository
+        repo.sign_revision('rev1', bzrlib.gpg.LoopbackGPGStrategy(None))
+        return repo
+
+    def test_fetch_copies_signatures(self):
+        source_repo = self.makeARepoWithSignatures()
+        target_repo = self.make_repository('target')
+        target_repo.fetch(source_repo, revision_id=None)
+        self.assertEqual(
+            source_repo.get_signature_text('rev1'),
+            target_repo.get_signature_text('rev1'))
+
     def test_get_revision_delta(self):
         tree_a = self.make_branch_and_tree('a')
         self.build_tree(['a/foo'])

# Begin bundle
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


More information about the bazaar mailing list