[MERGE] Refactor fetch: add get_data_about_revision_ids to repository, and other changes.
Andrew Bennetts
andrew at canonical.com
Wed Aug 8 07:22:24 BST 2007
Andrew Bennetts wrote:
[...]
> This particular bundle rearranges fetch.py a little. Most interestingly, it
> moves some of the logic out of fetch.py, and moves it to a new method on
> Repository, get_data_about_revision_ids(revision_ids).
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).
I discussed this over the phone with Robert, and the idea behind the new term
“item keys” is that the things returned by this function are basically keys to
the various kinds of repository data — this is related to Robert's work on a
pack file format.
I'd appreciate any feedback on the best way to describe this so that the
function's purpose is adequately clear (even though it's only used in one place
at the moment). Here's what I have at the moment:
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'.
"""
I'm hesistant to go into too much detail in this docstring... it seems to me
that as Robert's work lands we'll probably have better places for describing the
concepts than here.
I've attached the complete bundle for completeness sake, but it's basically the
same as before modulo the bits changed due to review comments.
Robert has suggested we might want to deprecate fileids_altered_by_revision_ids
as a public method, replacing it with this function. (Callers can just filter
out the non-file information). This seems like a good idea to me, Repository
already has a very large public interface and it would be good to shrink it
where we can. But that's a change for a different release cycle!
All that said, it's not a big deal to improve a docstring or rename an
little-used method later, so I'm not too concerned about this. I just want to
get another opinion or two as a sanity check.
-Andrew.
-------------- next part --------------
# Bazaar merge directive format 2 (Bazaar 0.19)
# revision_id: andrew.bennetts at canonical.com-20070808012954-\
# xcsz5ucoomy3f5c4
# target_branch: http://bazaar-vcs.org/bzr/bzr.dev
# testament_sha1: e663f0e58669686d7afafbf1d599c1a087da1eeb
# timestamp: 2007-08-08 16:18:45 +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 01:29:54 +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 get_data_to_fetch_for_revision_ids, 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
+ # get_data_to_fetch_for_revision_ids 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.get_data_to_fetch_for_revision_ids(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 00:40:14 +0000
@@ -636,6 +636,54 @@
pb.finished()
return result
+ def get_data_to_fetch_for_revision_ids(self, revision_ids, files_pb=None):
+ """Get an iterable about data for a given set of revision IDs.
+
+ The named data will be ordered so that it can be fetched and inserted in
+ that order safely.
+
+ :returns: (knit-kind, file-id, versions)
+ """
+ # 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