[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