/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transport/remote.py

  • Committer: Vincent Ladeuil
  • Date: 2007-06-20 14:25:06 UTC
  • mfrom: (2540 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2646.
  • Revision ID: v.ladeuil+lp@free.fr-20070620142506-txsb1v8538kpsafw
merge bzr.dev @ 2540

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""RemoteTransport client for the smart-server.
 
18
 
 
19
This module shouldn't be accessed directly.  The classes defined here should be
 
20
imported from bzrlib.smart.
 
21
"""
 
22
 
 
23
__all__ = ['RemoteTransport', 'RemoteTCPTransport', 'RemoteSSHTransport']
 
24
 
 
25
from cStringIO import StringIO
 
26
import urllib
 
27
import urlparse
 
28
 
 
29
from bzrlib import (
 
30
    errors,
 
31
    transport,
 
32
    urlutils,
 
33
    )
 
34
from bzrlib.smart import client, medium, protocol
 
35
 
 
36
# must do this otherwise urllib can't parse the urls properly :(
 
37
for scheme in ['ssh', 'bzr', 'bzr+loopback', 'bzr+ssh', 'bzr+http']:
 
38
    transport.register_urlparse_netloc_protocol(scheme)
 
39
del scheme
 
40
 
 
41
 
 
42
# Port 4155 is the default port for bzr://, registered with IANA.
 
43
BZR_DEFAULT_INTERFACE = '0.0.0.0'
 
44
BZR_DEFAULT_PORT = 4155
 
45
 
 
46
 
 
47
class _SmartStat(object):
 
48
 
 
49
    def __init__(self, size, mode):
 
50
        self.st_size = size
 
51
        self.st_mode = mode
 
52
 
 
53
 
 
54
class RemoteTransport(transport.ConnectedTransport):
 
55
    """Connection to a smart server.
 
56
 
 
57
    The connection holds references to the medium that can be used to send
 
58
    requests to the server.
 
59
 
 
60
    The connection has a notion of the current directory to which it's
 
61
    connected; this is incorporated in filenames passed to the server.
 
62
    
 
63
    This supports some higher-level RPC operations and can also be treated 
 
64
    like a Transport to do file-like operations.
 
65
 
 
66
    The connection can be made over a tcp socket, an ssh pipe or a series of
 
67
    http requests.  There are concrete subclasses for each type:
 
68
    RemoteTCPTransport, etc.
 
69
    """
 
70
 
 
71
    # IMPORTANT FOR IMPLEMENTORS: RemoteTransport MUST NOT be given encoding
 
72
    # responsibilities: Put those on SmartClient or similar. This is vital for
 
73
    # the ability to support multiple versions of the smart protocol over time:
 
74
    # RemoteTransport is an adapter from the Transport object model to the 
 
75
    # SmartClient model, not an encoder.
 
76
 
 
77
    # FIXME: the medium parameter should be private, only the tests requires
 
78
    # it. It may be even clearer to define a TestRemoteTransport that handles
 
79
    # the specific cases of providing a _client and/or a _medium, and leave
 
80
    # RemoteTransport as an abstract class.
 
81
    def __init__(self, url, from_transport=None, medium=None, _client=None):
 
82
        """Constructor.
 
83
 
 
84
        :param from_transport: Another RemoteTransport instance that this
 
85
            one is being cloned from.  Attributes such as the medium will
 
86
            be reused.
 
87
 
 
88
        :param medium: The medium to use for this RemoteTransport. This must be
 
89
            supplied if from_transport is None.
 
90
 
 
91
        :param _client: Override the _SmartClient used by this transport.  This
 
92
            should only be used for testing purposes; normally this is
 
93
            determined from the medium.
 
94
        """
 
95
        super(RemoteTransport, self).__init__(url, from_transport)
 
96
 
 
97
        if medium is None:
 
98
            medium = self._build_medium(from_transport)
 
99
        else:
 
100
            self._set_connection(medium, None)
 
101
        self._medium = medium
 
102
        assert self._medium is not None
 
103
        # ConnectedTransport provides the connection sharing by requiring
 
104
        # daughter classes to always access the connection via _get_connection
 
105
        # and guarantee that the connection wil be shared across all transports
 
106
        # even if the server force a reconnection (i.e. the connection object
 
107
        # should be built again).
 
108
 
 
109
        # For RemoteTransport objects, the medium is the connection. But using
 
110
        # _get_connection is not enough because:
 
111
        # - self._client needs a medium to be built (and keep an internal
 
112
        #   copy).
 
113
        # - RemoteBzrDir RemoteRepository, RemoteRepositoryFormat RemoteBranch
 
114
        #   keep internal copies of _SmartClient objects.
 
115
 
 
116
        # Therefore, the needed refactoring (enhancing _SmartClient and its
 
117
        # users so that connection are still shared even in cases or
 
118
        # reconnections) is too much for this round -- vila20070607
 
119
 
 
120
        # On the other hand, except for the reconnection part, the sharing will
 
121
        # already reduce the number of connections.
 
122
        if _client is None:
 
123
            self._client = client._SmartClient(self._medium)
 
124
        else:
 
125
            self._client = _client
 
126
 
 
127
    def _build_medium(self, from_transport=None):
 
128
        """Create the medium if from_transport does not provide one.
 
129
 
 
130
        The medium is analogous to the connection for ConnectedTransport: it
 
131
        allows connection sharing.
 
132
        
 
133
        :param from_transport: provide the medium to reuse if not None
 
134
        """
 
135
        if from_transport is not None:
 
136
            _medium = from_transport._medium
 
137
        else:
 
138
            _medium = None
 
139
            # No credentials
 
140
            self._set_connection(_medium, None)
 
141
        return _medium
 
142
 
 
143
    def is_readonly(self):
 
144
        """Smart server transport can do read/write file operations."""
 
145
        resp = self._call2('Transport.is_readonly')
 
146
        if resp == ('yes', ):
 
147
            return True
 
148
        elif resp == ('no', ):
 
149
            return False
 
150
        elif (resp == ('error', "Generic bzr smart protocol error: "
 
151
                                "bad request 'Transport.is_readonly'") or
 
152
              resp == ('error', "Generic bzr smart protocol error: "
 
153
                                "bad request u'Transport.is_readonly'")):
 
154
            # XXX: nasty hack: servers before 0.16 don't have a
 
155
            # 'Transport.is_readonly' verb, so we do what clients before 0.16
 
156
            # did: assume False.
 
157
            return False
 
158
        else:
 
159
            self._translate_error(resp)
 
160
        assert False, 'weird response %r' % (resp,)
 
161
 
 
162
    def get_smart_client(self):
 
163
        return self._get_connection()
 
164
 
 
165
    def get_smart_medium(self):
 
166
        return self._get_connection()
 
167
 
 
168
    def _remote_path(self, relpath):
 
169
        """Returns the Unicode version of the absolute path for relpath."""
 
170
        return self._combine_paths(self._path, relpath)
 
171
 
 
172
    def _call(self, method, *args):
 
173
        resp = self._call2(method, *args)
 
174
        self._translate_error(resp)
 
175
 
 
176
    def _call2(self, method, *args):
 
177
        """Call a method on the remote server."""
 
178
        return self._client.call(method, *args)
 
179
 
 
180
    def _call_with_body_bytes(self, method, args, body):
 
181
        """Call a method on the remote server with body bytes."""
 
182
        return self._client.call_with_body_bytes(method, args, body)
 
183
 
 
184
    def has(self, relpath):
 
185
        """Indicate whether a remote file of the given name exists or not.
 
186
 
 
187
        :see: Transport.has()
 
188
        """
 
189
        resp = self._call2('has', self._remote_path(relpath))
 
190
        if resp == ('yes', ):
 
191
            return True
 
192
        elif resp == ('no', ):
 
193
            return False
 
194
        else:
 
195
            self._translate_error(resp)
 
196
 
 
197
    def get(self, relpath):
 
198
        """Return file-like object reading the contents of a remote file.
 
199
        
 
200
        :see: Transport.get_bytes()/get_file()
 
201
        """
 
202
        return StringIO(self.get_bytes(relpath))
 
203
 
 
204
    def get_bytes(self, relpath):
 
205
        remote = self._remote_path(relpath)
 
206
        request = self._medium.get_request()
 
207
        smart_protocol = protocol.SmartClientRequestProtocolOne(request)
 
208
        smart_protocol.call('get', remote)
 
209
        resp = smart_protocol.read_response_tuple(True)
 
210
        if resp != ('ok', ):
 
211
            smart_protocol.cancel_read_body()
 
212
            self._translate_error(resp, relpath)
 
213
        return smart_protocol.read_body_bytes()
 
214
 
 
215
    def _serialise_optional_mode(self, mode):
 
216
        if mode is None:
 
217
            return ''
 
218
        else:
 
219
            return '%d' % mode
 
220
 
 
221
    def mkdir(self, relpath, mode=None):
 
222
        resp = self._call2('mkdir', self._remote_path(relpath),
 
223
            self._serialise_optional_mode(mode))
 
224
        self._translate_error(resp)
 
225
 
 
226
    def put_bytes(self, relpath, upload_contents, mode=None):
 
227
        # FIXME: upload_file is probably not safe for non-ascii characters -
 
228
        # should probably just pass all parameters as length-delimited
 
229
        # strings?
 
230
        if type(upload_contents) is unicode:
 
231
            # Although not strictly correct, we raise UnicodeEncodeError to be
 
232
            # compatible with other transports.
 
233
            raise UnicodeEncodeError(
 
234
                'undefined', upload_contents, 0, 1,
 
235
                'put_bytes must be given bytes, not unicode.')
 
236
        resp = self._call_with_body_bytes('put',
 
237
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
 
238
            upload_contents)
 
239
        self._translate_error(resp)
 
240
 
 
241
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
 
242
                             create_parent_dir=False,
 
243
                             dir_mode=None):
 
244
        """See Transport.put_bytes_non_atomic."""
 
245
        # FIXME: no encoding in the transport!
 
246
        create_parent_str = 'F'
 
247
        if create_parent_dir:
 
248
            create_parent_str = 'T'
 
249
 
 
250
        resp = self._call_with_body_bytes(
 
251
            'put_non_atomic',
 
252
            (self._remote_path(relpath), self._serialise_optional_mode(mode),
 
253
             create_parent_str, self._serialise_optional_mode(dir_mode)),
 
254
            bytes)
 
255
        self._translate_error(resp)
 
256
 
 
257
    def put_file(self, relpath, upload_file, mode=None):
 
258
        # its not ideal to seek back, but currently put_non_atomic_file depends
 
259
        # on transports not reading before failing - which is a faulty
 
260
        # assumption I think - RBC 20060915
 
261
        pos = upload_file.tell()
 
262
        try:
 
263
            return self.put_bytes(relpath, upload_file.read(), mode)
 
264
        except:
 
265
            upload_file.seek(pos)
 
266
            raise
 
267
 
 
268
    def put_file_non_atomic(self, relpath, f, mode=None,
 
269
                            create_parent_dir=False,
 
270
                            dir_mode=None):
 
271
        return self.put_bytes_non_atomic(relpath, f.read(), mode=mode,
 
272
                                         create_parent_dir=create_parent_dir,
 
273
                                         dir_mode=dir_mode)
 
274
 
 
275
    def append_file(self, relpath, from_file, mode=None):
 
276
        return self.append_bytes(relpath, from_file.read(), mode)
 
277
        
 
278
    def append_bytes(self, relpath, bytes, mode=None):
 
279
        resp = self._call_with_body_bytes(
 
280
            'append',
 
281
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
 
282
            bytes)
 
283
        if resp[0] == 'appended':
 
284
            return int(resp[1])
 
285
        self._translate_error(resp)
 
286
 
 
287
    def delete(self, relpath):
 
288
        resp = self._call2('delete', self._remote_path(relpath))
 
289
        self._translate_error(resp)
 
290
 
 
291
    def readv(self, relpath, offsets):
 
292
        if not offsets:
 
293
            return
 
294
 
 
295
        offsets = list(offsets)
 
296
 
 
297
        sorted_offsets = sorted(offsets)
 
298
        # turn the list of offsets into a stack
 
299
        offset_stack = iter(offsets)
 
300
        cur_offset_and_size = offset_stack.next()
 
301
        coalesced = list(self._coalesce_offsets(sorted_offsets,
 
302
                               limit=self._max_readv_combine,
 
303
                               fudge_factor=self._bytes_to_read_before_seek))
 
304
 
 
305
        request = self._medium.get_request()
 
306
        smart_protocol = protocol.SmartClientRequestProtocolOne(request)
 
307
        smart_protocol.call_with_body_readv_array(
 
308
            ('readv', self._remote_path(relpath)),
 
309
            [(c.start, c.length) for c in coalesced])
 
310
        resp = smart_protocol.read_response_tuple(True)
 
311
 
 
312
        if resp[0] != 'readv':
 
313
            # This should raise an exception
 
314
            smart_protocol.cancel_read_body()
 
315
            self._translate_error(resp)
 
316
            return
 
317
 
 
318
        # FIXME: this should know how many bytes are needed, for clarity.
 
319
        data = smart_protocol.read_body_bytes()
 
320
        # Cache the results, but only until they have been fulfilled
 
321
        data_map = {}
 
322
        for c_offset in coalesced:
 
323
            if len(data) < c_offset.length:
 
324
                raise errors.ShortReadvError(relpath, c_offset.start,
 
325
                            c_offset.length, actual=len(data))
 
326
            for suboffset, subsize in c_offset.ranges:
 
327
                key = (c_offset.start+suboffset, subsize)
 
328
                data_map[key] = data[suboffset:suboffset+subsize]
 
329
            data = data[c_offset.length:]
 
330
 
 
331
            # Now that we've read some data, see if we can yield anything back
 
332
            while cur_offset_and_size in data_map:
 
333
                this_data = data_map.pop(cur_offset_and_size)
 
334
                yield cur_offset_and_size[0], this_data
 
335
                cur_offset_and_size = offset_stack.next()
 
336
 
 
337
    def rename(self, rel_from, rel_to):
 
338
        self._call('rename',
 
339
                   self._remote_path(rel_from),
 
340
                   self._remote_path(rel_to))
 
341
 
 
342
    def move(self, rel_from, rel_to):
 
343
        self._call('move',
 
344
                   self._remote_path(rel_from),
 
345
                   self._remote_path(rel_to))
 
346
 
 
347
    def rmdir(self, relpath):
 
348
        resp = self._call('rmdir', self._remote_path(relpath))
 
349
 
 
350
    def _translate_error(self, resp, orig_path=None):
 
351
        """Raise an exception from a response"""
 
352
        if resp is None:
 
353
            what = None
 
354
        else:
 
355
            what = resp[0]
 
356
        if what == 'ok':
 
357
            return
 
358
        elif what == 'NoSuchFile':
 
359
            if orig_path is not None:
 
360
                error_path = orig_path
 
361
            else:
 
362
                error_path = resp[1]
 
363
            raise errors.NoSuchFile(error_path)
 
364
        elif what == 'error':
 
365
            raise errors.SmartProtocolError(unicode(resp[1]))
 
366
        elif what == 'FileExists':
 
367
            raise errors.FileExists(resp[1])
 
368
        elif what == 'DirectoryNotEmpty':
 
369
            raise errors.DirectoryNotEmpty(resp[1])
 
370
        elif what == 'ShortReadvError':
 
371
            raise errors.ShortReadvError(resp[1], int(resp[2]),
 
372
                                         int(resp[3]), int(resp[4]))
 
373
        elif what in ('UnicodeEncodeError', 'UnicodeDecodeError'):
 
374
            encoding = str(resp[1]) # encoding must always be a string
 
375
            val = resp[2]
 
376
            start = int(resp[3])
 
377
            end = int(resp[4])
 
378
            reason = str(resp[5]) # reason must always be a string
 
379
            if val.startswith('u:'):
 
380
                val = val[2:].decode('utf-8')
 
381
            elif val.startswith('s:'):
 
382
                val = val[2:].decode('base64')
 
383
            if what == 'UnicodeDecodeError':
 
384
                raise UnicodeDecodeError(encoding, val, start, end, reason)
 
385
            elif what == 'UnicodeEncodeError':
 
386
                raise UnicodeEncodeError(encoding, val, start, end, reason)
 
387
        elif what == "ReadOnlyError":
 
388
            raise errors.TransportNotPossible('readonly transport')
 
389
        else:
 
390
            raise errors.SmartProtocolError('unexpected smart server error: %r' % (resp,))
 
391
 
 
392
    def disconnect(self):
 
393
        self._medium.disconnect()
 
394
 
 
395
    def delete_tree(self, relpath):
 
396
        raise errors.TransportNotPossible('readonly transport')
 
397
 
 
398
    def stat(self, relpath):
 
399
        resp = self._call2('stat', self._remote_path(relpath))
 
400
        if resp[0] == 'stat':
 
401
            return _SmartStat(int(resp[1]), int(resp[2], 8))
 
402
        else:
 
403
            self._translate_error(resp)
 
404
 
 
405
    ## def lock_read(self, relpath):
 
406
    ##     """Lock the given file for shared (read) access.
 
407
    ##     :return: A lock object, which should be passed to Transport.unlock()
 
408
    ##     """
 
409
    ##     # The old RemoteBranch ignore lock for reading, so we will
 
410
    ##     # continue that tradition and return a bogus lock object.
 
411
    ##     class BogusLock(object):
 
412
    ##         def __init__(self, path):
 
413
    ##             self.path = path
 
414
    ##         def unlock(self):
 
415
    ##             pass
 
416
    ##     return BogusLock(relpath)
 
417
 
 
418
    def listable(self):
 
419
        return True
 
420
 
 
421
    def list_dir(self, relpath):
 
422
        resp = self._call2('list_dir', self._remote_path(relpath))
 
423
        if resp[0] == 'names':
 
424
            return [name.encode('ascii') for name in resp[1:]]
 
425
        else:
 
426
            self._translate_error(resp)
 
427
 
 
428
    def iter_files_recursive(self):
 
429
        resp = self._call2('iter_files_recursive', self._remote_path(''))
 
430
        if resp[0] == 'names':
 
431
            return resp[1:]
 
432
        else:
 
433
            self._translate_error(resp)
 
434
 
 
435
 
 
436
class RemoteTCPTransport(RemoteTransport):
 
437
    """Connection to smart server over plain tcp.
 
438
    
 
439
    This is essentially just a factory to get 'RemoteTransport(url,
 
440
        SmartTCPClientMedium).
 
441
    """
 
442
 
 
443
    def __init__(self, base, from_transport=None):
 
444
        assert base.startswith('bzr://')
 
445
        super(RemoteTCPTransport, self).__init__(base, from_transport)
 
446
        if self._port is None:
 
447
            self._port = BZR_DEFAULT_PORT
 
448
 
 
449
    def _build_medium(self, from_transport=None):
 
450
        if from_transport is not None:
 
451
            _medium = from_transport._medium
 
452
        else:
 
453
            _medium = medium.SmartTCPClientMedium(self._host, self._port)
 
454
            self._set_connection(_medium, None)
 
455
        return _medium
 
456
 
 
457
 
 
458
class RemoteSSHTransport(RemoteTransport):
 
459
    """Connection to smart server over SSH.
 
460
 
 
461
    This is essentially just a factory to get 'RemoteTransport(url,
 
462
        SmartSSHClientMedium).
 
463
    """
 
464
 
 
465
    def _build_medium(self, from_transport=None):
 
466
        assert self.base.startswith('bzr+ssh://')
 
467
        if from_transport is not None:
 
468
            _medium = from_transport._medium
 
469
        else:
 
470
            _medium = medium.SmartSSHClientMedium(self._host, self._port,
 
471
                                                  self._user, self._password)
 
472
            # ssh will prompt the user for a password if needed and if none is
 
473
            # provided but it will not give it back, so no credentials can be
 
474
            # stored.
 
475
            self._set_connection(_medium, None)
 
476
        return _medium
 
477
 
 
478
 
 
479
class RemoteHTTPTransport(RemoteTransport):
 
480
    """Just a way to connect between a bzr+http:// url and http://.
 
481
    
 
482
    This connection operates slightly differently than the RemoteSSHTransport.
 
483
    It uses a plain http:// transport underneath, which defines what remote
 
484
    .bzr/smart URL we are connected to. From there, all paths that are sent are
 
485
    sent as relative paths, this way, the remote side can properly
 
486
    de-reference them, since it is likely doing rewrite rules to translate an
 
487
    HTTP path into a local path.
 
488
    """
 
489
 
 
490
    def __init__(self, base, from_transport=None, http_transport=None):
 
491
        assert base.startswith('bzr+http://')
 
492
 
 
493
        if http_transport is None:
 
494
            # FIXME: the password may be lost here because it appears in the
 
495
            # url only for an intial construction (when the url came from the
 
496
            # command-line).
 
497
            http_url = base[len('bzr+'):]
 
498
            self._http_transport = transport.get_transport(http_url)
 
499
        else:
 
500
            self._http_transport = http_transport
 
501
        super(RemoteHTTPTransport, self).__init__(base, from_transport)
 
502
 
 
503
    def _build_medium(self, from_transport=None):
 
504
        if from_transport is not None:
 
505
            _medium = from_transport._medium
 
506
        else:
 
507
            _medium = self._http_transport.get_smart_medium()
 
508
            # We let http_transport take care of the credentials
 
509
            self._set_connection(_medium, None)
 
510
        return _medium
 
511
 
 
512
    def _remote_path(self, relpath):
 
513
        """After connecting, HTTP Transport only deals in relative URLs."""
 
514
        # Adjust the relpath based on which URL this smart transport is
 
515
        # connected to.
 
516
        http_base = urlutils.normalize_url(self._http_transport.base)
 
517
        url = urlutils.join(self.base[len('bzr+'):], relpath)
 
518
        url = urlutils.normalize_url(url)
 
519
        return urlutils.relative_url(http_base, url)
 
520
 
 
521
    def clone(self, relative_url):
 
522
        """Make a new RemoteHTTPTransport related to me.
 
523
 
 
524
        This is re-implemented rather than using the default
 
525
        RemoteTransport.clone() because we must be careful about the underlying
 
526
        http transport.
 
527
 
 
528
        Also, the cloned smart transport will POST to the same .bzr/smart
 
529
        location as this transport (although obviously the relative paths in the
 
530
        smart requests may be different).  This is so that the server doesn't
 
531
        have to handle .bzr/smart requests at arbitrary places inside .bzr
 
532
        directories, just at the initial URL the user uses.
 
533
 
 
534
        The exception is parent paths (i.e. relative_url of "..").
 
535
        """
 
536
        if relative_url:
 
537
            abs_url = self.abspath(relative_url)
 
538
        else:
 
539
            abs_url = self.base
 
540
        # We either use the exact same http_transport (for child locations), or
 
541
        # a clone of the underlying http_transport (for parent locations).  This
 
542
        # means we share the connection.
 
543
        norm_base = urlutils.normalize_url(self.base)
 
544
        norm_abs_url = urlutils.normalize_url(abs_url)
 
545
        normalized_rel_url = urlutils.relative_url(norm_base, norm_abs_url)
 
546
        if normalized_rel_url == ".." or normalized_rel_url.startswith("../"):
 
547
            http_transport = self._http_transport.clone(normalized_rel_url)
 
548
        else:
 
549
            http_transport = self._http_transport
 
550
        return RemoteHTTPTransport(abs_url, self, http_transport=http_transport)
 
551
 
 
552
 
 
553
def get_test_permutations():
 
554
    """Return (transport, server) permutations for testing."""
 
555
    ### We may need a little more test framework support to construct an
 
556
    ### appropriate RemoteTransport in the future.
 
557
    from bzrlib.smart import server
 
558
    return [(RemoteTCPTransport, server.SmartTCPServer_for_testing)]