Rev 5695: (spiv) Add Branch.heads_to_fetch API and HPSS request. (Andrew Bennetts) in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Thu Mar 3 06:02:53 UTC 2011


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 5695 [merge]
revision-id: pqm at pqm.ubuntu.com-20110303060249-l2zou9i59742nrqf
parent: pqm at pqm.ubuntu.com-20110303043323-76p182iwtfnaqbkt
parent: andrew.bennetts at canonical.com-20110301080749-2fv6l3d4t9o7umfm
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Thu 2011-03-03 06:02:49 +0000
message:
  (spiv) Add Branch.heads_to_fetch API and HPSS request. (Andrew Bennetts)
modified:
  bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
  bzrlib/fetch.py                fetch.py-20050818234941-26fea6105696365d
  bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
  bzrlib/smart/branch.py         branch.py-20061124031907-mzh3pla28r83r97f-1
  bzrlib/smart/request.py        request.py-20061108095550-gunadhxmzkdjfeek-1
  bzrlib/tests/per_branch/test_branch.py testbranch.py-20050711070244-121d632bc37d7253
  bzrlib/tests/test_remote.py    test_remote.py-20060720103555-yeeg2x51vn0rbtdp-2
  doc/en/release-notes/bzr-2.4.txt bzr2.4.txt-20110114053217-k7ym9jfz243fddjm-1
=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py	2011-02-26 15:35:01 +0000
+++ b/bzrlib/branch.py	2011-03-03 06:02:49 +0000
@@ -1531,6 +1531,26 @@
         else:
             raise AssertionError("invalid heads: %r" % (heads,))
 
+    def heads_to_fetch(self):
+        """Return the heads that must and that should be fetched to copy this
+        branch into another repo.
+
+        :returns: a 2-tuple of (must_fetch, if_present_fetch).  must_fetch is a
+            set of heads that must be fetched.  if_present_fetch is a set of
+            heads that must be fetched if present, but no error is necessary if
+            they are not present.
+        """
+        # For bzr native formats must_fetch is just the tip, and if_present_fetch
+        # are the tags.
+        must_fetch = set([self.last_revision()])
+        try:
+            if_present_fetch = set(self.tags.get_reverse_tag_dict())
+        except errors.TagsNotSupported:
+            if_present_fetch = set()
+        must_fetch.discard(_mod_revision.NULL_REVISION)
+        if_present_fetch.discard(_mod_revision.NULL_REVISION)
+        return must_fetch, if_present_fetch
+
 
 class BranchFormat(controldir.ControlComponentFormat):
     """An encapsulation of the initialization and open routines for a format.

=== modified file 'bzrlib/fetch.py'
--- a/bzrlib/fetch.py	2011-02-07 04:14:29 +0000
+++ b/bzrlib/fetch.py	2011-02-25 03:00:35 +0000
@@ -350,7 +350,8 @@
 
     Factors that go into determining the sort of fetch to perform:
      * did the caller specify any revision IDs?
-     * did the caller specify a source branch (need to fetch the tip + tags)
+     * did the caller specify a source branch (need to fetch its
+       heads_to_fetch(), usually the tip + tags)
      * is there an existing target repo (don't need to refetch revs it
        already has)
      * target is stacked?  (similar to pre-existing target repo: even if
@@ -391,27 +392,29 @@
                 return graph.EverythingNotInOther(
                     self.target_repo, self.source_repo).execute()
         heads_to_fetch = set(self._explicit_rev_ids)
-        tags_to_fetch = set()
         if self.source_branch is not None:
-            try:
-                tags_to_fetch.update(
-                    self.source_branch.tags.get_reverse_tag_dict())
-            except errors.TagsNotSupported:
-                pass
+            must_fetch, if_present_fetch = self.source_branch.heads_to_fetch()
             if self.source_branch_stop_revision_id is not None:
-                heads_to_fetch.add(self.source_branch_stop_revision_id)
-            else:
-                heads_to_fetch.add(self.source_branch.last_revision())
+                # Replace the tip rev from must_fetch with the stop revision
+                # XXX: this might be wrong if the tip rev is also in the
+                # must_fetch set for other reasons (e.g. it's the tip of
+                # multiple loom threads?), but then it's pretty unclear what it
+                # should mean to specify a stop_revision in that case anyway.
+                must_fetch.discard(self.source_branch.last_revision())
+                must_fetch.add(self.source_branch_stop_revision_id)
+            heads_to_fetch.update(must_fetch)
+        else:
+            if_present_fetch = set()
         if self.target_repo_kind == TargetRepoKinds.EMPTY:
             # PendingAncestryResult does not raise errors if a requested head
             # is absent.  Ideally it would support the
             # required_ids/if_present_ids distinction, but in practice
             # heads_to_fetch will almost certainly be present so this doesn't
             # matter much.
-            all_heads = heads_to_fetch.union(tags_to_fetch)
+            all_heads = heads_to_fetch.union(if_present_fetch)
             return graph.PendingAncestryResult(all_heads, self.source_repo)
         return graph.NotInOtherForRevs(self.target_repo, self.source_repo,
-            required_ids=heads_to_fetch, if_present_ids=tags_to_fetch
+            required_ids=heads_to_fetch, if_present_ids=if_present_fetch
             ).execute()
 
 

=== modified file 'bzrlib/remote.py'
--- a/bzrlib/remote.py	2011-03-03 04:33:23 +0000
+++ b/bzrlib/remote.py	2011-03-03 06:02:49 +0000
@@ -2194,6 +2194,19 @@
         self._ensure_real()
         return self._custom_format.supports_set_append_revisions_only()
 
+    def _use_default_local_heads_to_fetch(self):
+        # If the branch format is a metadir format *and* its heads_to_fetch
+        # implementation is not overridden vs the base class, we can use the
+        # base class logic rather than use the heads_to_fetch RPC.  This is
+        # usually cheaper in terms of net round trips, as the last-revision and
+        # tags info fetched is cached and would be fetched anyway.
+        self._ensure_real()
+        if isinstance(self._custom_format, branch.BranchFormatMetadir):
+            branch_class = self._custom_format._branch_class()
+            heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
+            if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
+                return True
+        return False
 
 class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
     """Branch stored on a server accessed by HPSS RPC.
@@ -2781,6 +2794,33 @@
         self._ensure_real()
         return self._real_branch.set_push_location(location)
 
+    def heads_to_fetch(self):
+        if self._format._use_default_local_heads_to_fetch():
+            # We recognise this format, and its heads-to-fetch implementation
+            # is the default one (tip + tags).  In this case it's cheaper to
+            # just use the default implementation rather than a special RPC as
+            # the tip and tags data is cached.
+            return branch.Branch.heads_to_fetch(self)
+        medium = self._client._medium
+        if medium._is_remote_before((2, 4)):
+            return self._vfs_heads_to_fetch()
+        try:
+            return self._rpc_heads_to_fetch()
+        except errors.UnknownSmartMethod:
+            medium._remember_remote_is_before((2, 4))
+            return self._vfs_heads_to_fetch()
+
+    def _rpc_heads_to_fetch(self):
+        response = self._call('Branch.heads_to_fetch', self._remote_path())
+        if len(response) != 2:
+            raise errors.UnexpectedSmartServerResponse(response)
+        must_fetch, if_present_fetch = response
+        return set(must_fetch), set(if_present_fetch)
+
+    def _vfs_heads_to_fetch(self):
+        self._ensure_real()
+        return self._real_branch.heads_to_fetch()
+
 
 class RemoteConfig(object):
     """A Config that reads and writes from smart verbs.

=== modified file 'bzrlib/smart/branch.py'
--- a/bzrlib/smart/branch.py	2010-05-13 16:17:54 +0000
+++ b/bzrlib/smart/branch.py	2011-02-25 04:44:53 +0000
@@ -142,6 +142,20 @@
             self.branch.unlock()
 
 
+class SmartServerBranchHeadsToFetch(SmartServerBranchRequest):
+
+    def do_with_branch(self, branch):
+        """Return the heads-to-fetch for a Branch as two bencoded lists.
+        
+        See Branch.heads_to_fetch.
+
+        New in 2.4.
+        """
+        must_fetch, if_present_fetch = branch.heads_to_fetch()
+        return SuccessfulSmartServerResponse(
+            (list(must_fetch), list(if_present_fetch)))
+
+
 class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
 
     def do_with_branch(self, branch):

=== modified file 'bzrlib/smart/request.py'
--- a/bzrlib/smart/request.py	2011-03-02 20:39:58 +0000
+++ b/bzrlib/smart/request.py	2011-03-03 06:02:49 +0000
@@ -505,6 +505,9 @@
     'Branch.set_tags_bytes', 'bzrlib.smart.branch',
     'SmartServerBranchSetTagsBytes')
 request_handlers.register_lazy(
+    'Branch.heads_to_fetch', 'bzrlib.smart.branch',
+    'SmartServerBranchHeadsToFetch')
+request_handlers.register_lazy(
     'Branch.get_stacked_on_url', 'bzrlib.smart.branch', 'SmartServerBranchRequestGetStackedOnURL')
 request_handlers.register_lazy(
     'Branch.last_revision_info', 'bzrlib.smart.branch', 'SmartServerBranchRequestLastRevisionInfo')

=== modified file 'bzrlib/tests/per_branch/test_branch.py'
--- a/bzrlib/tests/per_branch/test_branch.py	2011-02-19 17:37:45 +0000
+++ b/bzrlib/tests/per_branch/test_branch.py	2011-02-21 07:27:20 +0000
@@ -467,6 +467,26 @@
         br.set_revision_history([])
         self.assertEquals(br.revision_history(), [])
 
+    def test_heads_to_fetch(self):
+        # heads_to_fetch is a method that returns a collection of revids that
+        # need to be fetched to copy this branch into another repo.  At a
+        # minimum this will include the tip.
+        # (In native formats, this is the tip + tags, but other formats may
+        # have other revs needed)
+        tree = self.make_branch_and_tree('a')
+        tree.commit('first commit', rev_id='rev1')
+        tree.commit('second commit', rev_id='rev2')
+        must_fetch, should_fetch = tree.branch.heads_to_fetch()
+        self.assertTrue('rev2' in must_fetch)
+
+    def test_heads_to_fetch_not_null_revision(self):
+        # NULL_REVISION does not appear in the result of heads_to_fetch, even
+        # for an empty branch.
+        tree = self.make_branch_and_tree('a')
+        must_fetch, should_fetch = tree.branch.heads_to_fetch()
+        self.assertFalse(revision.NULL_REVISION in must_fetch)
+        self.assertFalse(revision.NULL_REVISION in should_fetch)
+
 
 class TestBranchFormat(per_branch.TestCaseWithBranch):
 

=== modified file 'bzrlib/tests/test_remote.py'
--- a/bzrlib/tests/test_remote.py	2011-03-03 04:33:23 +0000
+++ b/bzrlib/tests/test_remote.py	2011-03-03 06:02:49 +0000
@@ -1143,6 +1143,71 @@
             [('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
 
 
+class TestBranchHeadsToFetch(RemoteBranchTestCase):
+
+    def test_uses_last_revision_info_and_tags_by_default(self):
+        transport = MemoryTransport()
+        client = FakeClient(transport.base)
+        client.add_expected_call(
+            'Branch.get_stacked_on_url', ('quack/',),
+            'error', ('NotStacked',))
+        client.add_expected_call(
+            'Branch.last_revision_info', ('quack/',),
+            'success', ('ok', '1', 'rev-tip'))
+        # XXX: this will break if the default format's serialization of tags
+        # changes, or if the RPC for fetching tags changes from get_tags_bytes.
+        client.add_expected_call(
+            'Branch.get_tags_bytes', ('quack/',),
+            'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
+        transport.mkdir('quack')
+        transport = transport.clone('quack')
+        branch = self.make_remote_branch(transport, client)
+        result = branch.heads_to_fetch()
+        self.assertFinished(client)
+        self.assertEqual(
+            (set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
+
+    def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
+        transport = MemoryTransport()
+        client = FakeClient(transport.base)
+        client.add_expected_call(
+            'Branch.get_stacked_on_url', ('quack/',),
+            'error', ('NotStacked',))
+        client.add_expected_call(
+            'Branch.heads_to_fetch', ('quack/',),
+            'success', (['tip'], ['tagged-1', 'tagged-2']))
+        transport.mkdir('quack')
+        transport = transport.clone('quack')
+        branch = self.make_remote_branch(transport, client)
+        branch._format._use_default_local_heads_to_fetch = lambda: False
+        result = branch.heads_to_fetch()
+        self.assertFinished(client)
+        self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
+
+    def test_backwards_compatible(self):
+        self.setup_smart_server_with_call_log()
+        # Make a branch with a single revision.
+        builder = self.make_branch_builder('foo')
+        builder.start_series()
+        builder.build_snapshot('tip', None, [
+            ('add', ('', 'root-id', 'directory', ''))])
+        builder.finish_series()
+        branch = builder.get_branch()
+        # Add two tags to that branch
+        branch.tags.set_tag('tag-1', 'rev-1')
+        branch.tags.set_tag('tag-2', 'rev-2')
+        self.addCleanup(branch.lock_read().unlock)
+        # Disable the heads_to_fetch verb
+        verb = 'Branch.heads_to_fetch'
+        self.disable_verb(verb)
+        self.reset_smart_call_log()
+        result = branch.heads_to_fetch()
+        self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
+        self.assertEqual(
+            ['Branch.last_revision_info', 'Branch.get_tags_bytes'],
+            [call.call.method for call in self.hpss_calls])
+
+
 class TestBranchLastRevisionInfo(RemoteBranchTestCase):
 
     def test_empty_branch(self):

=== modified file 'doc/en/release-notes/bzr-2.4.txt'
--- a/doc/en/release-notes/bzr-2.4.txt	2011-03-03 04:33:23 +0000
+++ b/doc/en/release-notes/bzr-2.4.txt	2011-03-03 06:02:49 +0000
@@ -44,6 +44,10 @@
   the dirstate file to be rebuilt, rather than using a ``bzr checkout``
   workaround. (John Arbash Meinel)
 
+* Added a ``Branch.heads_to_fetch`` RPC to the smart server protocol.
+  This allows formats from plugins (such as looms) to efficiently tell the
+  client which revisions need to be fetched.  (Andrew Bennetts)
+
 * Branching, merging and pulling a branch now copies revisions named in
   tags, not just the tag metadata.  (Andrew Bennetts, #309682)
   
@@ -133,6 +137,9 @@
 .. Changes that may require updates in plugins or other code that uses
    bzrlib.
 
+* Added ``Branch.heads_to_fetch`` method.  Implementions of the Branch API
+  must now inherit or implement this method.  (Andrew Bennetts, #721328)
+  
 * Added ``bzrlib.mergetools`` module with helper functions for working with
   the list of external merge tools. (Gordon Tyler, #489915)
 




More information about the bazaar-commits mailing list