[MERGE][#230550] Fix spurious "No repository present" errors when accessing branches in shared repos via bzr+http.

Andrew Bennetts andrew at canonical.com
Mon May 19 15:14:49 BST 2008


This patch fixes <https://bugs.launchpad.net/bzr/+bug/230550>, which has been
marked Critical as it appears to be a regression since 1.3.1.

The problem was that although a smart medium can be intrinsically connected to a
particular location, that location was being tracked in the _SmartClient object
rather than the SmartClientMedium.  This made it possible for the 'base'
attribute of the _SmartClient to be inconsistent with actual location the
'medium' attribute was connected to, and sure enough the slightly confused logic
in RemoteTransport/RemoteHTTPTransport caused this to happen.  The result is
false "No repository present" errors when accessing a shared repository via
bzr+http, as described in the bug report.

So I've removed the 'base' parameter (and instance attribute) from _SmartClient
and put it on SmartClientMedium where it belongs.  This removes the room for
error in tracking the right base to use when sharing a medium between
RemoteTransport.

This change had some cosmetic knock-on effects in a bunch of
test_smart_transport tests which has inflated the diff size a bit.  I also took
the opportunity to make the RemoteTransport.__init__ docstring match reality,
and to reduce the nested ifs in that method a little.

I haven't added any new tests, but I did add some new assertions to
RemoteHTTPTransportTestCase that fail without these changes. 

-Andrew.

-------------- next part --------------
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: andrew.bennetts at canonical.com-20080519140208-\
#   qxh0sazud0e8n43f
# target_branch: http://bazaar-vcs.org/bzr/bzr.dev
# testament_sha1: 4d5b047fd5283f772f476b8310c3e4077f25c561
# timestamp: 2008-05-20 00:02:11 +1000
# source_branch: http://people.ubuntu.com/~andrew/bzr/bug-230550
# base_revision_id: pqm at pqm.ubuntu.com-20080518104127-7dezgqfdvymf4xvu
# 
# Begin patch
=== modified file 'NEWS'
--- NEWS	2008-05-17 17:53:47 +0000
+++ NEWS	2008-05-19 14:02:08 +0000
@@ -27,6 +27,11 @@
 
   BUGFIXES:
 
+    * Correctly track the base URL of a smart medium when using bzr+http://
+      URLs, which was causing spurious "No repository present" errors with
+      branches in shared repositories accessed over bzr+http.
+      (Andrew Bennetts, #230550)
+
     * Issue a warning and ignore passwords declared in authentication.conf when
       used for an ssh scheme (sftp or bzr+ssh).
       (Vincent Ladeuil, #203186)

=== modified file 'bzrlib/bzrdir.py'
--- bzrlib/bzrdir.py	2008-05-16 05:59:21 +0000
+++ bzrlib/bzrdir.py	2008-05-19 08:42:32 +0000
@@ -2414,7 +2414,7 @@
             # TODO: lookup the local format from a server hint.
             local_dir_format = BzrDirMetaFormat1()
             return local_dir_format.initialize_on_transport(transport)
-        client = _SmartClient(client_medium, transport.base)
+        client = _SmartClient(client_medium)
         path = client.remote_path_from_transport(transport)
         response = client.call('BzrDirFormat.initialize', path)
         if response[0] != 'ok':

=== modified file 'bzrlib/remote.py'
--- bzrlib/remote.py	2008-05-16 06:06:35 +0000
+++ bzrlib/remote.py	2008-05-19 08:42:32 +0000
@@ -62,7 +62,7 @@
 
         if _client is None:
             medium = transport.get_smart_medium()
-            self._client = client._SmartClient(medium, transport.base)
+            self._client = client._SmartClient(medium)
         else:
             self._client = _client
             return

=== modified file 'bzrlib/smart/client.py'
--- bzrlib/smart/client.py	2008-05-16 07:32:33 +0000
+++ bzrlib/smart/client.py	2008-05-19 08:42:32 +0000
@@ -24,14 +24,12 @@
 
 class _SmartClient(object):
 
-    def __init__(self, medium, base, headers=None):
+    def __init__(self, medium, headers=None):
         """Constructor.
 
         :param medium: a SmartClientMedium
-        :param base: a URL
         """
         self._medium = medium
-        self._base = base
         if headers is None:
             self._headers = {'Software version': bzrlib.__version__}
         else:
@@ -157,13 +155,21 @@
         anything but path, so it is only safe to use it in requests sent over
         the medium from the matching transport.
         """
-        base = self._base
-        if (base.startswith('bzr+http://') or base.startswith('bzr+https://')
-            or base.startswith('http://') or base.startswith('https://')):
-            medium_base = self._base
+        base = self._medium.base
+        if base.startswith('bzr+'):
+            base = base[4:]
+        if (base.startswith('http://') or base.startswith('https://')):
+            # XXX: There seems to be a bug here: http+urllib:// and
+            # http+pycurl:// ought to be treated the same as http://, I think.
+            #   - Andrew Bennetts, 2008-05-19.
+            medium_base = base
         else:
-            medium_base = urlutils.join(self._base, '/')
+            medium_base = urlutils.join(base, '/')
+
+        transport_base = transport.base
+        if transport_base.startswith('bzr+'):
+            transport_base = transport_base[4:]
             
-        rel_url = urlutils.relative_url(medium_base, transport.base)
+        rel_url = urlutils.relative_url(medium_base, transport_base)
         return urllib.unquote(rel_url)
 

=== modified file 'bzrlib/smart/medium.py'
--- bzrlib/smart/medium.py	2008-05-16 07:05:26 +0000
+++ bzrlib/smart/medium.py	2008-05-19 10:55:48 +0000
@@ -431,8 +431,9 @@
 class SmartClientMedium(object):
     """Smart client is a medium for sending smart protocol requests over."""
 
-    def __init__(self):
+    def __init__(self, base):
         super(SmartClientMedium, self).__init__()
+        self.base = base
         self._protocol_version_error = None
         self._protocol_version = None
         self._done_hello = False
@@ -488,8 +489,8 @@
     receive bytes.
     """
 
-    def __init__(self):
-        SmartClientMedium.__init__(self)
+    def __init__(self, base):
+        SmartClientMedium.__init__(self, base)
         self._current_request = None
         # Be optimistic: we assume the remote end can accept new remote
         # requests until we get an error saying otherwise.  (1.2 adds some
@@ -531,8 +532,8 @@
     This client does not manage the pipes: it assumes they will always be open.
     """
 
-    def __init__(self, readable_pipe, writeable_pipe):
-        SmartClientStreamMedium.__init__(self)
+    def __init__(self, readable_pipe, writeable_pipe, base):
+        SmartClientStreamMedium.__init__(self, base)
         self._readable_pipe = readable_pipe
         self._writeable_pipe = writeable_pipe
 
@@ -553,13 +554,13 @@
     """A client medium using SSH."""
     
     def __init__(self, host, port=None, username=None, password=None,
-            vendor=None, bzr_remote_path=None):
+            base=None, vendor=None, bzr_remote_path=None):
         """Creates a client that will connect on the first use.
         
         :param vendor: An optional override for the ssh vendor to use. See
             bzrlib.transport.ssh for details on ssh vendors.
         """
-        SmartClientStreamMedium.__init__(self)
+        SmartClientStreamMedium.__init__(self, base)
         self._connected = False
         self._host = host
         self._password = password
@@ -625,9 +626,9 @@
 class SmartTCPClientMedium(SmartClientStreamMedium):
     """A client medium using TCP."""
     
-    def __init__(self, host, port):
+    def __init__(self, host, port, base):
         """Creates a client that will connect on the first use."""
-        SmartClientStreamMedium.__init__(self)
+        SmartClientStreamMedium.__init__(self, base)
         self._connected = False
         self._host = host
         self._port = port

=== modified file 'bzrlib/tests/blackbox/test_serve.py'
--- bzrlib/tests/blackbox/test_serve.py	2008-03-27 00:27:41 +0000
+++ bzrlib/tests/blackbox/test_serve.py	2008-05-19 11:43:41 +0000
@@ -80,10 +80,10 @@
         # Connect to the server
         # We use this url because while this is no valid URL to connect to this
         # server instance, the transport needs a URL.
+        url = 'bzr://localhost/'
         client_medium = medium.SmartSimplePipesClientMedium(
-            process.stdout, process.stdin)
-        transport = remote.RemoteTransport(
-            'bzr://localhost/', medium=client_medium)
+            process.stdout, process.stdin, url)
+        transport = remote.RemoteTransport(url, medium=client_medium)
         return process, transport
 
     def start_server_port(self, extra_options=()):

=== modified file 'bzrlib/tests/test_remote.py'
--- bzrlib/tests/test_remote.py	2008-05-16 05:59:21 +0000
+++ bzrlib/tests/test_remote.py	2008-05-19 08:42:32 +0000
@@ -48,7 +48,7 @@
 from bzrlib.symbol_versioning import one_four
 from bzrlib.transport import get_transport
 from bzrlib.transport.memory import MemoryTransport
-from bzrlib.transport.remote import RemoteTransport
+from bzrlib.transport.remote import RemoteTransport, RemoteTCPTransport
 
 
 class BasicRemoteObjectTests(tests.TestCaseWithTransport):
@@ -144,7 +144,7 @@
         self.responses = []
         self._calls = []
         self.expecting_body = False
-        _SmartClient.__init__(self, FakeMedium(self._calls), fake_medium_base)
+        _SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
 
     def add_success_response(self, *args):
         self.responses.append(('success', args, None))
@@ -186,9 +186,10 @@
 
 class FakeMedium(object):
 
-    def __init__(self, client_calls):
+    def __init__(self, client_calls, base):
         self._remote_is_at_least_1_2 = True
         self._client_calls = client_calls
+        self.base = base
 
     def disconnect(self):
         self._client_calls.append(('disconnect medium',))
@@ -215,8 +216,9 @@
         """Assert that the result of _SmartClient.remote_path_from_transport
         is the expected value for a given client_base and transport_base.
         """
-        dummy_medium = 'dummy medium'
-        client = _SmartClient(dummy_medium, client_base)
+        class DummyMedium(object):
+            base = client_base
+        client = _SmartClient(DummyMedium())
         transport = get_transport(transport_base)
         result = client.remote_path_from_transport(transport)
         self.assertEqual(expected, result)
@@ -291,7 +293,7 @@
     def test_url_quoting_of_path(self):
         # Relpaths on the wire should not be URL-escaped.  So "~" should be
         # transmitted as "~", not "%7E".
-        transport = RemoteTransport('bzr://localhost/~hello/')
+        transport = RemoteTCPTransport('bzr://localhost/~hello/')
         client = FakeClient(transport.base)
         client.add_success_response('ok', '')
         client.add_success_response('ok', '', 'no', 'no', 'no')

=== modified file 'bzrlib/tests/test_smart_transport.py'
--- bzrlib/tests/test_smart_transport.py	2008-05-16 07:15:57 +0000
+++ bzrlib/tests/test_smart_transport.py	2008-05-19 13:55:43 +0000
@@ -115,7 +115,7 @@
         sock.bind(('127.0.0.1', 0))
         sock.listen(1)
         port = sock.getsockname()[1]
-        client_medium = medium.SmartTCPClientMedium('127.0.0.1', port)
+        client_medium = medium.SmartTCPClientMedium('127.0.0.1', port, 'base')
         return sock, client_medium
 
     def receive_bytes_on_server(self, sock, bytes):
@@ -133,27 +133,16 @@
         t.start()
         return t
     
-    def test_construct_smart_stream_medium_client(self):
-        # make a new instance of the common base for Stream-like Mediums.
-        # this just ensures that the constructor stays parameter-free which
-        # is important for reuse : some subclasses will dynamically connect,
-        # others are always on, etc.
-        client_medium = medium.SmartClientStreamMedium()
-
-    def test_construct_smart_client_medium(self):
-        # the base client medium takes no parameters
-        client_medium = medium.SmartClientMedium()
-    
     def test_construct_smart_simple_pipes_client_medium(self):
         # the SimplePipes client medium takes two pipes:
         # readable pipe, writeable pipe.
         # Constructing one should just save these and do nothing.
         # We test this by passing in None.
-        client_medium = medium.SmartSimplePipesClientMedium(None, None)
+        client_medium = medium.SmartSimplePipesClientMedium(None, None, None)
         
     def test_simple_pipes_client_request_type(self):
         # SimplePipesClient should use SmartClientStreamMediumRequest's.
-        client_medium = medium.SmartSimplePipesClientMedium(None, None)
+        client_medium = medium.SmartSimplePipesClientMedium(None, None, None)
         request = client_medium.get_request()
         self.assertIsInstance(request, medium.SmartClientStreamMediumRequest)
 
@@ -165,7 +154,8 @@
         # classes - as the sibling classes share this logic, they do not have
         # explicit tests for this.
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(None, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            None, output, 'base')
         request = client_medium.get_request()
         request.finished_writing()
         request.finished_reading()
@@ -176,7 +166,8 @@
     def test_simple_pipes_client__accept_bytes_writes_to_writable(self):
         # accept_bytes writes to the writeable pipe.
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(None, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            None, output, 'base')
         client_medium._accept_bytes('abc')
         self.assertEqual('abc', output.getvalue())
     
@@ -184,7 +175,8 @@
         # calling disconnect does nothing.
         input = StringIO()
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         # send some bytes to ensure disconnecting after activity still does not
         # close.
         client_medium._accept_bytes('abc')
@@ -197,7 +189,8 @@
         # accept_bytes writes to.
         input = StringIO()
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         client_medium._accept_bytes('abc')
         client_medium.disconnect()
         client_medium._accept_bytes('abc')
@@ -208,14 +201,14 @@
     def test_simple_pipes_client_ignores_disconnect_when_not_connected(self):
         # Doing a disconnect on a new (and thus unconnected) SimplePipes medium
         # does nothing.
-        client_medium = medium.SmartSimplePipesClientMedium(None, None)
+        client_medium = medium.SmartSimplePipesClientMedium(None, None, 'base')
         client_medium.disconnect()
 
     def test_simple_pipes_client_can_always_read(self):
         # SmartSimplePipesClientMedium is never disconnected, so read_bytes
         # always tries to read from the underlying pipe.
         input = StringIO('abcdef')
-        client_medium = medium.SmartSimplePipesClientMedium(input, None)
+        client_medium = medium.SmartSimplePipesClientMedium(input, None, 'base')
         self.assertEqual('abc', client_medium.read_bytes(3))
         client_medium.disconnect()
         self.assertEqual('def', client_medium.read_bytes(3))
@@ -230,7 +223,8 @@
         flush_calls = []
         def logging_flush(): flush_calls.append('flush')
         output.flush = logging_flush
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         # this call is here to ensure we only flush once, not on every
         # _accept_bytes call.
         client_medium._accept_bytes('abc')
@@ -250,7 +244,7 @@
         # having vendor be invalid means that if it tries to connect via the
         # vendor it will blow up.
         client_medium = medium.SmartSSHClientMedium('127.0.0.1', unopened_port,
-            username=None, password=None, vendor="not a vendor",
+            username=None, password=None, base='base', vendor="not a vendor",
             bzr_remote_path='bzr')
         sock.close()
 
@@ -260,7 +254,8 @@
         output = StringIO()
         vendor = StringIOSSHVendor(StringIO(), output)
         client_medium = medium.SmartSSHClientMedium(
-            'a hostname', 'a port', 'a username', 'a password', vendor, 'bzr')
+            'a hostname', 'a port', 'a username', 'a password', 'base', vendor,
+            'bzr')
         client_medium._accept_bytes('abc')
         self.assertEqual('abc', output.getvalue())
         self.assertEqual([('connect_ssh', 'a username', 'a password',
@@ -281,7 +276,7 @@
         client_medium = self.callDeprecated(
             ['bzr_remote_path is required as of bzr 0.92'],
             medium.SmartSSHClientMedium, 'a hostname', 'a port', 'a username',
-            'a password', vendor)
+            'a password', 'base', vendor)
         client_medium._accept_bytes('abc')
         self.assertEqual('abc', output.getvalue())
         self.assertEqual([('connect_ssh', 'a username', 'a password',
@@ -295,7 +290,7 @@
         output = StringIO()
         vendor = StringIOSSHVendor(StringIO(), output)
         client_medium = medium.SmartSSHClientMedium('a hostname', 'a port',
-            'a username', 'a password', vendor, bzr_remote_path='fugly')
+            'a username', 'a password', 'base', vendor, bzr_remote_path='fugly')
         client_medium._accept_bytes('abc')
         self.assertEqual('abc', output.getvalue())
         self.assertEqual([('connect_ssh', 'a username', 'a password',
@@ -309,9 +304,8 @@
         input = StringIO()
         output = StringIO()
         vendor = StringIOSSHVendor(input, output)
-        client_medium = medium.SmartSSHClientMedium('a hostname',
-                                                    vendor=vendor,
-                                                    bzr_remote_path='bzr')
+        client_medium = medium.SmartSSHClientMedium(
+            'a hostname', base='base', vendor=vendor, bzr_remote_path='bzr')
         client_medium._accept_bytes('abc')
         client_medium.disconnect()
         self.assertTrue(input.closed)
@@ -331,8 +325,8 @@
         input = StringIO()
         output = StringIO()
         vendor = StringIOSSHVendor(input, output)
-        client_medium = medium.SmartSSHClientMedium('a hostname',
-            vendor=vendor, bzr_remote_path='bzr')
+        client_medium = medium.SmartSSHClientMedium(
+            'a hostname', base='base', vendor=vendor, bzr_remote_path='bzr')
         client_medium._accept_bytes('abc')
         client_medium.disconnect()
         # the disconnect has closed output, so we need a new output for the
@@ -360,15 +354,15 @@
     def test_ssh_client_ignores_disconnect_when_not_connected(self):
         # Doing a disconnect on a new (and thus unconnected) SSH medium
         # does not fail.  It's ok to disconnect an unconnected medium.
-        client_medium = medium.SmartSSHClientMedium(None,
-                                                    bzr_remote_path='bzr')
+        client_medium = medium.SmartSSHClientMedium(
+            None, base='base', bzr_remote_path='bzr')
         client_medium.disconnect()
 
     def test_ssh_client_raises_on_read_when_not_connected(self):
         # Doing a read on a new (and thus unconnected) SSH medium raises
         # MediumNotConnected.
-        client_medium = medium.SmartSSHClientMedium(None,
-                                                    bzr_remote_path='bzr')
+        client_medium = medium.SmartSSHClientMedium(
+            None, base='base', bzr_remote_path='bzr')
         self.assertRaises(errors.MediumNotConnected, client_medium.read_bytes,
                           0)
         self.assertRaises(errors.MediumNotConnected, client_medium.read_bytes,
@@ -385,9 +379,8 @@
         def logging_flush(): flush_calls.append('flush')
         output.flush = logging_flush
         vendor = StringIOSSHVendor(input, output)
-        client_medium = medium.SmartSSHClientMedium('a hostname',
-                                                    vendor=vendor,
-                                                    bzr_remote_path='bzr')
+        client_medium = medium.SmartSSHClientMedium(
+            'a hostname', base='base', vendor=vendor, bzr_remote_path='bzr')
         # this call is here to ensure we only flush once, not on every
         # _accept_bytes call.
         client_medium._accept_bytes('abc')
@@ -401,7 +394,8 @@
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         sock.bind(('127.0.0.1', 0))
         unopened_port = sock.getsockname()[1]
-        client_medium = medium.SmartTCPClientMedium('127.0.0.1', unopened_port)
+        client_medium = medium.SmartTCPClientMedium(
+            '127.0.0.1', unopened_port, 'base')
         sock.close()
 
     def test_tcp_client_connects_on_first_use(self):
@@ -435,13 +429,13 @@
     def test_tcp_client_ignores_disconnect_when_not_connected(self):
         # Doing a disconnect on a new (and thus unconnected) TCP medium
         # does not fail.  It's ok to disconnect an unconnected medium.
-        client_medium = medium.SmartTCPClientMedium(None, None)
+        client_medium = medium.SmartTCPClientMedium(None, None, None)
         client_medium.disconnect()
 
     def test_tcp_client_raises_on_read_when_not_connected(self):
         # Doing a read on a new (and thus unconnected) TCP medium raises
         # MediumNotConnected.
-        client_medium = medium.SmartTCPClientMedium(None, None)
+        client_medium = medium.SmartTCPClientMedium(None, None, None)
         self.assertRaises(errors.MediumNotConnected, client_medium.read_bytes, 0)
         self.assertRaises(errors.MediumNotConnected, client_medium.read_bytes, 1)
 
@@ -467,7 +461,7 @@
     def test_tcp_client_host_unknown_connection_error(self):
         self.requireFeature(InvalidHostnameFeature)
         client_medium = medium.SmartTCPClientMedium(
-            'non_existent.invalid', 4155)
+            'non_existent.invalid', 4155, 'base')
         self.assertRaises(
             errors.ConnectionError, client_medium._ensure_connection)
 
@@ -485,7 +479,8 @@
         # WritingCompleted to prevent bad assumptions on stream environments
         # breaking the needs of message-based environments.
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(None, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            None, output, 'base')
         request = medium.SmartClientStreamMediumRequest(client_medium)
         request.finished_writing()
         self.assertRaises(errors.WritingCompleted, request.accept_bytes, None)
@@ -496,7 +491,8 @@
         # and checking that the pipes get the data.
         input = StringIO()
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = medium.SmartClientStreamMediumRequest(client_medium)
         request.accept_bytes('123')
         request.finished_writing()
@@ -508,7 +504,8 @@
         # constructing a SmartClientStreamMediumRequest on a StreamMedium sets
         # the current request to the new SmartClientStreamMediumRequest
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(None, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            None, output, 'base')
         request = medium.SmartClientStreamMediumRequest(client_medium)
         self.assertIs(client_medium._current_request, request)
 
@@ -516,7 +513,8 @@
         # constructing a SmartClientStreamMediumRequest on a StreamMedium with
         # a non-None _current_request raises TooManyConcurrentRequests.
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(None, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            None, output, 'base')
         client_medium._current_request = "a"
         self.assertRaises(errors.TooManyConcurrentRequests,
             medium.SmartClientStreamMediumRequest, client_medium)
@@ -525,7 +523,8 @@
         # calling finished_reading clears the current request from the requests
         # medium
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(None, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            None, output, 'base')
         request = medium.SmartClientStreamMediumRequest(client_medium)
         request.finished_writing()
         request.finished_reading()
@@ -534,7 +533,8 @@
     def test_finished_read_before_finished_write_errors(self):
         # calling finished_reading before calling finished_writing triggers a
         # WritingNotComplete error.
-        client_medium = medium.SmartSimplePipesClientMedium(None, None)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            None, None, 'base')
         request = medium.SmartClientStreamMediumRequest(client_medium)
         self.assertRaises(errors.WritingNotComplete, request.finished_reading)
         
@@ -547,7 +547,8 @@
         # smoke tests.
         input = StringIO('321')
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = medium.SmartClientStreamMediumRequest(client_medium)
         request.finished_writing()
         self.assertEqual('321', request.read_bytes(3))
@@ -560,7 +561,7 @@
         # WritingNotComplete error because the Smart protocol is designed to be
         # compatible with strict message based protocols like HTTP where the
         # request cannot be submitted until the writing has completed.
-        client_medium = medium.SmartSimplePipesClientMedium(None, None)
+        client_medium = medium.SmartSimplePipesClientMedium(None, None, 'base')
         request = medium.SmartClientStreamMediumRequest(client_medium)
         self.assertRaises(errors.WritingNotComplete, request.read_bytes, None)
 
@@ -569,7 +570,8 @@
         # ReadingCompleted to prevent bad assumptions on stream environments
         # breaking the needs of message-based environments.
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(None, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            None, output, 'base')
         request = medium.SmartClientStreamMediumRequest(client_medium)
         request.finished_writing()
         request.finished_reading()
@@ -1372,7 +1374,8 @@
         # We want to be able to pass a client as a parameter to RemoteTransport.
         input = StringIO('ok\n3\nbardone\n')
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         transport = remote.RemoteTransport(
             'bzr://localhost/', medium=client_medium)
         # Disable version detection.
@@ -1394,7 +1397,7 @@
 
     def test__translate_error_readonly(self):
         """Sending a ReadOnlyError to _translate_error raises TransportNotPossible."""
-        client_medium = medium.SmartClientMedium()
+        client_medium = medium.SmartSimplePipesClientMedium(None, None, 'base')
         transport = remote.RemoteTransport(
             'bzr://localhost/', medium=client_medium)
         self.assertRaises(errors.TransportNotPossible,
@@ -1433,7 +1436,8 @@
         else:
             input = StringIO(input_bytes)
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         if self.client_protocol_class is not None:
             client_protocol = self.client_protocol_class(request)
@@ -1566,7 +1570,8 @@
     def test_construct_version_one_client_protocol(self):
         # we can construct a client protocol from a client medium request
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(None, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            None, output, 'base')
         request = client_medium.get_request()
         client_protocol = protocol.SmartClientRequestProtocolOne(request)
 
@@ -1664,7 +1669,8 @@
         # the error if the response is a non-understood version.
         input = StringIO('ok\x012\n')
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = protocol.SmartClientRequestProtocolOne(request)
         self.assertEqual(2, smart_protocol.query_version())
@@ -1687,7 +1693,8 @@
         expected_bytes = "foo\n7\nabcdefgdone\n"
         input = StringIO("\n")
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = protocol.SmartClientRequestProtocolOne(request)
         smart_protocol.call_with_body_bytes(('foo', ), "abcdefg")
@@ -1699,7 +1706,8 @@
         expected_bytes = "foo\n7\n1,2\n5,6done\n"
         input = StringIO("\n")
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = protocol.SmartClientRequestProtocolOne(request)
         smart_protocol.call_with_body_readv_array(('foo', ), [(1,2),(5,6)])
@@ -1709,7 +1717,8 @@
             server_bytes):
         input = StringIO(server_bytes)
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = protocol.SmartClientRequestProtocolOne(request)
         smart_protocol.call('foo')
@@ -1747,7 +1756,8 @@
         server_bytes = "ok\n7\n1234567done\n"
         input = StringIO(server_bytes)
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = protocol.SmartClientRequestProtocolOne(request)
         smart_protocol.call('foo')
@@ -1764,7 +1774,8 @@
         server_bytes = "ok\n7\n1234567done\n"
         input = StringIO(server_bytes)
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = protocol.SmartClientRequestProtocolOne(request)
         smart_protocol.call('foo')
@@ -1781,7 +1792,8 @@
         server_bytes = "ok\n7\n1234567done\n"
         input = StringIO(server_bytes)
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = protocol.SmartClientRequestProtocolOne(request)
         smart_protocol.call('foo')
@@ -1811,7 +1823,8 @@
     def test_construct_version_two_client_protocol(self):
         # we can construct a client protocol from a client medium request
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(None, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            None, output, 'base')
         request = client_medium.get_request()
         client_protocol = protocol.SmartClientRequestProtocolTwo(request)
 
@@ -1914,7 +1927,8 @@
         # the error if the response is a non-understood version.
         input = StringIO(self.response_marker + 'success\nok\x012\n')
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = self.client_protocol_class(request)
         self.assertEqual(2, smart_protocol.query_version())
@@ -1940,7 +1954,8 @@
         expected_bytes = self.request_marker + "foo\n7\nabcdefgdone\n"
         input = StringIO("\n")
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = self.client_protocol_class(request)
         smart_protocol.call_with_body_bytes(('foo', ), "abcdefg")
@@ -1952,7 +1967,8 @@
         expected_bytes = self.request_marker + "foo\n7\n1,2\n5,6done\n"
         input = StringIO("\n")
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = self.client_protocol_class(request)
         smart_protocol.call_with_body_readv_array(('foo', ), [(1,2),(5,6)])
@@ -1966,7 +1982,8 @@
                         "success\nok\n7\n1234567done\n")
         input = StringIO(server_bytes)
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = self.client_protocol_class(request)
         smart_protocol.call('foo')
@@ -1983,7 +2000,8 @@
         server_bytes = self.response_marker + "success\nok\n7\n1234567done\n"
         input = StringIO(server_bytes)
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = self.client_protocol_class(request)
         smart_protocol.call('foo')
@@ -1999,7 +2017,8 @@
         server_bytes = self.response_marker + "success\nok\n7\n1234567done\n"
         input = StringIO(server_bytes)
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = self.client_protocol_class(request)
         smart_protocol.call('foo')
@@ -2102,7 +2121,8 @@
                         body_terminator)
         input = StringIO(server_bytes)
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = protocol.SmartClientRequestProtocolTwo(request)
         smart_protocol.call('foo')
@@ -2122,7 +2142,8 @@
                         "success\nok\n" + body)
         input = StringIO(server_bytes)
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         smart_request = client_medium.get_request()
         smart_protocol = protocol.SmartClientRequestProtocolTwo(smart_request)
         smart_protocol.call('foo')
@@ -2137,7 +2158,8 @@
         server_bytes = protocol.RESPONSE_VERSION_TWO + "success\nok\n"
         input = StringIO(server_bytes)
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = protocol.SmartClientRequestProtocolTwo(request)
         smart_protocol.call('foo')
@@ -2154,7 +2176,8 @@
             "error\x01Generic bzr smart protocol error: bad request 'foo'\n")
         input = StringIO(server_bytes)
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'base')
         request = client_medium.get_request()
         smart_protocol = protocol.SmartClientRequestProtocolTwo(request)
         smart_protocol.call('foo')
@@ -2332,7 +2355,7 @@
         protocol_decoder.state_accept = protocol_decoder._state_accept_expecting_message_part
         output = StringIO()
         client_medium = medium.SmartSimplePipesClientMedium(
-            StringIO(interrupted_body_stream), output)
+            StringIO(interrupted_body_stream), output, 'base')
         medium_request = client_medium.get_request()
         medium_request.finished_writing()
         response_handler.setProtoAndMediumRequest(
@@ -2585,8 +2608,9 @@
         """
         input = StringIO("\n")
         output = StringIO()
-        client_medium = medium.SmartSimplePipesClientMedium(input, output)
-        smart_client = client._SmartClient(client_medium, 'ignored base')
+        client_medium = medium.SmartSimplePipesClientMedium(
+            input, output, 'ignored base')
+        smart_client = client._SmartClient(client_medium)
         self.assertRaises(TypeError,
             smart_client.call_with_body_bytes, method, args, body)
         self.assertEqual("", output.getvalue())
@@ -2749,7 +2773,7 @@
     def test_version_three_server(self):
         """With a protocol 3 server, only one request is needed."""
         medium = MockMedium()
-        smart_client = client._SmartClient(medium, 'base', headers={})
+        smart_client = client._SmartClient(medium, headers={})
         message_start = protocol.MESSAGE_VERSION_THREE + '\x00\x00\x00\x02de'
         medium.expect_request(
             message_start +
@@ -2769,7 +2793,7 @@
         use protocol 2 immediately.
         """
         medium = MockMedium()
-        smart_client = client._SmartClient(medium, 'base', headers={})
+        smart_client = client._SmartClient(medium, headers={})
         # First the client should send a v3 request, but the server will reply
         # with a v2 error.
         medium.expect_request(
@@ -2803,7 +2827,7 @@
         protocol version, a SmartProtocolError is raised.
         """
         medium = MockMedium()
-        smart_client = client._SmartClient(medium, 'base', headers={})
+        smart_client = client._SmartClient(medium, headers={})
         unknown_protocol_bytes = 'Unknown protocol!'
         # The client will try v3 and v2 before eventually giving up.
         medium.expect_request(
@@ -2827,7 +2851,7 @@
         """ProtocolThreeRequester.call by default sends a 'Software
         version' header.
         """
-        smart_client = client._SmartClient(medium, 'base')
+        smart_client = client._SmartClient('dummy medium')
         self.assertEqual(
             bzrlib.__version__, smart_client._headers['Software version'])
         # XXX: need a test that smart_client._headers is passed to the request
@@ -3158,6 +3182,9 @@
         self.assertEqual(base_transport._http_transport,
                          new_transport._http_transport)
         self.assertEqual('child_dir/foo', new_transport._remote_path('foo'))
+        self.assertEqual(
+            'child_dir/',
+            new_transport._client.remote_path_from_transport(new_transport))
 
     def test_remote_path_unnormal_base(self):
         # If the transport's base isn't normalised, the _remote_path should
@@ -3171,6 +3198,9 @@
         base_transport = remote.RemoteHTTPTransport('bzr+http://host/%7Ea/b')
         new_transport = base_transport.clone('c')
         self.assertEqual('bzr+http://host/%7Ea/b/c/', new_transport.base)
+        self.assertEqual(
+            'c/',
+            new_transport._client.remote_path_from_transport(new_transport))
 
         
 # TODO: Client feature that does get_bundle and then installs that into a

=== modified file 'bzrlib/transport/__init__.py'
--- bzrlib/transport/__init__.py	2008-05-08 04:33:38 +0000
+++ bzrlib/transport/__init__.py	2008-05-19 10:55:48 +0000
@@ -293,7 +293,7 @@
     _bytes_to_read_before_seek = 0
 
     def __init__(self, base):
-        super(Transport, self).__init__()
+        super(Transport, self).__init__(base=base)
         self.base = base
 
     def _translate_error(self, e, path, raise_generic=True):

=== modified file 'bzrlib/transport/remote.py'
--- bzrlib/transport/remote.py	2008-05-16 05:55:23 +0000
+++ bzrlib/transport/remote.py	2008-05-19 11:20:29 +0000
@@ -77,8 +77,10 @@
             one is being cloned from.  Attributes such as the medium will
             be reused.
 
-        :param medium: The medium to use for this RemoteTransport. This must be
-            supplied if _from_transport is None.
+        :param medium: The medium to use for this RemoteTransport.  If None,
+            the medium from the _from_transport is shared.  If both this
+            and _from_transport are None, a new medium will be built.
+            _from_transport and medium cannot both be specified.
 
         :param _client: Override the _SmartClient used by this transport.  This
             should only be used for testing purposes; normally this is
@@ -103,14 +105,18 @@
             self._shared_connection = transport._SharedConnection(medium,
                                                                   credentials,
                                                                   self.base)
+        elif medium is None:
+            # No medium was specified, so share the medium from the
+            # _from_transport.
+            medium = self._shared_connection.connection
         else:
-            if medium is None:
-                # No medium was specified, so share the medium from the
-                # _from_transport.
-                medium = self._shared_connection.connection
+            raise AssertionError(
+                "Both _from_transport (%r) and medium (%r) passed to "
+                "RemoteTransport.__init__, but these parameters are mutally "
+                "exclusive." % (_from_transport, medium))
 
         if _client is None:
-            self._client = client._SmartClient(medium, self.base)
+            self._client = client._SmartClient(medium)
         else:
             self._client = _client
 
@@ -464,7 +470,9 @@
     """
 
     def _build_medium(self):
-        return medium.SmartTCPClientMedium(self._host, self._port), None
+        client_medium = medium.SmartTCPClientMedium(
+            self._host, self._port, self.base)
+        return client_medium, None
 
 
 class RemoteSSHTransport(RemoteTransport):
@@ -480,8 +488,10 @@
         # stored.
         location_config = config.LocationConfig(self.base)
         bzr_remote_path = location_config.get_bzr_remote_path()
-        return medium.SmartSSHClientMedium(self._host, self._port,
-            self._user, self._password, bzr_remote_path=bzr_remote_path), None
+        client_medium = medium.SmartSSHClientMedium(self._host, self._port,
+            self._user, self._password, self.base,
+            bzr_remote_path=bzr_remote_path)
+        return client_medium, None
 
 
 class RemoteHTTPTransport(RemoteTransport):

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


More information about the bazaar mailing list