[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