Rev 2820: [MERGE][0.91] Make RemoteRepository.sprout cope gracefully with servers that don't support the 'Repository.tarball' request. in http://sourcefrog.net/bzr/0.91-integration

Martin Pool mbp at sourcefrog.net
Wed Sep 26 01:53:10 BST 2007


At http://sourcefrog.net/bzr/0.91-integration

------------------------------------------------------------
revno: 2820
revision-id: mbp at sourcefrog.net-20070926005308-30n4v1t2lejhkjap
parent: mbp at sourcefrog.net-20070926005005-xbsj9nwk6b3jgw48
parent: andrew.bennetts at canonical.com-20070914025541-xymskacw08apuoq5
committer: Martin Pool <mbp at sourcefrog.net>
branch nick: 0.91-integration
timestamp: Wed 2007-09-26 10:53:08 +1000
message:
  [MERGE][0.91] Make RemoteRepository.sprout cope gracefully with servers that don't support the 'Repository.tarball' request.
modified:
  bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
  bzrlib/tests/test_remote.py    test_remote.py-20060720103555-yeeg2x51vn0rbtdp-2
    ------------------------------------------------------------
    revno: 2816.2.3
    revision-id: andrew.bennetts at canonical.com-20070914025541-xymskacw08apuoq5
    parent: andrew.bennetts at canonical.com-20070914024300-om8lbs7nag4n44u4
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: tarball-back-compat-for-0.91
    timestamp: Fri 2007-09-14 12:55:41 +1000
    message:
      Improve the test.
    modified:
      bzrlib/tests/test_remote.py    test_remote.py-20060720103555-yeeg2x51vn0rbtdp-2
    ------------------------------------------------------------
    revno: 2816.2.2
    revision-id: andrew.bennetts at canonical.com-20070914024300-om8lbs7nag4n44u4
    parent: andrew.bennetts at canonical.com-20070914013818-yvv1e53dsc6gagss
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: tarball-back-compat-for-0.91
    timestamp: Fri 2007-09-14 12:43:00 +1000
    message:
      Make the fallback a little tidier.
    modified:
      bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
    ------------------------------------------------------------
    revno: 2816.2.1
    revision-id: andrew.bennetts at canonical.com-20070914013818-yvv1e53dsc6gagss
    parent: pqm at pqm.ubuntu.com-20070912051907-80aaitfew8esgq23
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: tarball-back-compat-for-0.91
    timestamp: Fri 2007-09-14 11:38:18 +1000
    message:
      Cope gracefully if the server doesn't support the Repository.tarball smart request.
    modified:
      bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
      bzrlib/tests/test_remote.py    test_remote.py-20060720103555-yeeg2x51vn0rbtdp-2
=== modified file 'bzrlib/remote.py'
--- a/bzrlib/remote.py	2007-09-11 08:03:10 +0000
+++ b/bzrlib/remote.py	2007-09-14 02:43:00 +0000
@@ -500,13 +500,14 @@
         return self._real_repository.break_lock()
 
     def _get_tarball(self, compression):
-        """Return a TemporaryFile containing a repository tarball"""
+        """Return a TemporaryFile containing a repository tarball.
+        
+        Returns None if the server does not support sending tarballs.
+        """
         import tempfile
         path = self.bzrdir._path_for_remote_call(self._client)
         response, protocol = self._client.call_expecting_body(
             'Repository.tarball', path, compression)
-        assert response[0] in ('ok', 'failure'), \
-            'unexpected response code %s' % (response,)
         if response[0] == 'ok':
             # Extract the tarball and return it
             t = tempfile.NamedTemporaryFile()
@@ -514,14 +515,23 @@
             t.write(protocol.read_body_bytes())
             t.seek(0)
             return t
-        else:
-            raise errors.SmartServerError(error_code=response)
+        if (response == ('error', "Generic bzr smart protocol error: "
+                "bad request 'Repository.tarball'") or
+              response == ('error', "Generic bzr smart protocol error: "
+                "bad request u'Repository.tarball'")):
+            protocol.cancel_read_body()
+            return None
+        raise errors.UnexpectedSmartServerResponse(response)
 
     def sprout(self, to_bzrdir, revision_id=None):
         # TODO: Option to control what format is created?
-        to_repo = to_bzrdir.create_repository()
-        self._copy_repository_tarball(to_repo, revision_id)
-        return to_repo
+        to_repo = self._copy_repository_tarball(to_bzrdir, revision_id)
+        if to_repo is None:
+            self._ensure_real()
+            return self._real_repository.sprout(
+                to_bzrdir, revision_id=revision_id)
+        else:
+            return to_repo
 
     ### These methods are just thin shims to the VFS object for now.
 
@@ -677,7 +687,7 @@
         return self._real_repository.copy_content_into(
             destination, revision_id=revision_id)
 
-    def _copy_repository_tarball(self, destination, revision_id=None):
+    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
         # get a tarball of the remote repository, and copy from that into the
         # destination
         from bzrlib import osutils
@@ -687,6 +697,9 @@
         # TODO: Maybe a progress bar while streaming the tarball?
         note("Copying repository content as tarball...")
         tar_file = self._get_tarball('bz2')
+        if tar_file is None:
+            return None
+        destination = to_bzrdir.create_repository()
         try:
             tar = tarfile.open('repository', fileobj=tar_file,
                 mode='r|bz2')
@@ -700,9 +713,7 @@
                 osutils.rmtree(tmpdir)
         finally:
             tar_file.close()
-        # TODO: if the server doesn't support this operation, maybe do it the
-        # slow way using the _real_repository?
-        #
+        return destination
         # TODO: Suggestion from john: using external tar is much faster than
         # python's tarfile library, but it may not work on windows.
 

=== modified file 'bzrlib/tests/test_remote.py'
--- a/bzrlib/tests/test_remote.py	2007-07-19 06:34:09 +0000
+++ b/bzrlib/tests/test_remote.py	2007-09-14 02:55:41 +0000
@@ -106,11 +106,18 @@
 class FakeProtocol(object):
     """Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
 
-    def __init__(self, body):
+    def __init__(self, body, fake_client):
         self._body_buffer = StringIO(body)
+        self._fake_client = fake_client
 
     def read_body_bytes(self, count=-1):
-        return self._body_buffer.read(count)
+        bytes = self._body_buffer.read(count)
+        if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
+            self._fake_client.expecting_body = False
+        return bytes
+
+    def cancel_read_body(self):
+        self._fake_client.expecting_body = False
 
 
 class FakeClient(_SmartClient):
@@ -118,13 +125,14 @@
     
     def __init__(self, responses):
         # We don't call the super init because there is no medium.
-        """create a FakeClient.
+        """Create a FakeClient.
 
         :param respones: A list of response-tuple, body-data pairs to be sent
             back to callers.
         """
         self.responses = responses
         self._calls = []
+        self.expecting_body = False
 
     def call(self, method, *args):
         self._calls.append(('call', method, args))
@@ -133,7 +141,8 @@
     def call_expecting_body(self, method, *args):
         self._calls.append(('call_expecting_body', method, args))
         result = self.responses.pop(0)
-        return result[0], FakeProtocol(result[1])
+        self.expecting_body = True
+        return result[0], FakeProtocol(result[1], self)
 
 
 class TestBzrDirOpenBranch(tests.TestCase):
@@ -751,6 +760,49 @@
         # try to copy...
         remote_repo.sprout(dest_bzrdir)
 
+    def test_backwards_compatibility(self):
+        """If the server doesn't recognise this request, fallback to VFS.
+        
+        This happens when a current client talks to an older server that
+        doesn't implement 'Repository.tarball'.
+        """
+        # Make a regular local repository to receive the results
+        dest_transport = MemoryTransport()
+        dest_transport.mkdir('destrepo')
+        bzrdir_format = bzrdir.format_registry.make_bzrdir('default')
+        dest_bzrdir = bzrdir_format.initialize_on_transport(dest_transport)
+
+        error_msg = (
+            "Generic bzr smart protocol error: "
+            "bad request 'Repository.tarball'")
+        responses = [(('error', error_msg), '')]
+        remote_repo, client = self.setup_fake_client_and_repository(
+            responses, 'path')
+        mock_real_repo = MockRealRepository()
+        remote_repo._real_repository = mock_real_repo
+
+        # try to copy...
+        remote_repo.sprout(dest_bzrdir)
+
+        self.assertEqual([('sprout', dest_bzrdir, None)], mock_real_repo.calls,
+            "RemoteRepository didn't fallback to the real repository correctly")
+        self.failIf(client.expecting_body,
+            "The protocol has been left in an unclean state that will cause "
+            "TooManyConcurrentRequests errors.")
+
+
+class MockRealRepository(object):
+    """Mock of a RemoteRepository's '_real_repository' attribute.
+    
+    Used by TestRepositoryTarball.test_backwards_compatibility.
+    """
+
+    def __init__(self):
+        self.calls = []
+
+    def sprout(self, to_bzrdir, revision_id=None):
+        self.calls.append(('sprout', to_bzrdir, revision_id))
+
 
 class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
     """RemoteRepository.copy_content_into optimizations"""




More information about the bazaar-commits mailing list