~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/__init__.py

  • Committer: Robert Collins
  • Date: 2007-07-04 08:08:13 UTC
  • mfrom: (2572 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2587.
  • Revision ID: robertc@robertcollins.net-20070704080813-wzebx0r88fvwj5rq
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
26
26
it.
27
27
"""
28
28
 
 
29
from cStringIO import StringIO
 
30
import re
 
31
import sys
 
32
 
 
33
from bzrlib.lazy_import import lazy_import
 
34
lazy_import(globals(), """
29
35
import errno
30
36
from collections import deque
31
 
from copy import deepcopy
32
 
from cStringIO import StringIO
33
 
import re
34
37
from stat import S_ISDIR
35
 
import sys
36
 
from unittest import TestSuite
 
38
import unittest
37
39
import urllib
38
40
import urlparse
39
41
import warnings
45
47
    symbol_versioning,
46
48
    urlutils,
47
49
    )
48
 
from bzrlib.errors import DependencyNotPresent
49
 
from bzrlib.osutils import pumpfile
 
50
""")
 
51
 
50
52
from bzrlib.symbol_versioning import (
51
53
        deprecated_passed,
52
54
        deprecated_method,
55
57
        zero_eight,
56
58
        zero_eleven,
57
59
        )
58
 
from bzrlib.trace import mutter, warning
59
 
 
60
 
# {prefix: [transport_classes]}
61
 
# Transports are inserted onto the list LIFO and tried in order; as a result
62
 
# transports provided by plugins are tried first, which is usually what we
63
 
# want.
64
 
_protocol_handlers = {
65
 
}
66
 
 
67
 
def register_transport(prefix, klass, override=DEPRECATED_PARAMETER):
68
 
    """Register a transport that can be used to open URLs
69
 
 
70
 
    Normally you should use register_lazy_transport, which defers loading the
71
 
    implementation until it's actually used, and so avoids pulling in possibly
72
 
    large implementation libraries.
73
 
    """
74
 
    # Note that this code runs very early in library setup -- trace may not be
75
 
    # working, etc.
76
 
    global _protocol_handlers
77
 
    if deprecated_passed(override):
78
 
        warnings.warn("register_transport(override) is deprecated")
79
 
    _protocol_handlers.setdefault(prefix, []).insert(0, klass)
80
 
 
81
 
 
82
 
def register_lazy_transport(scheme, module, classname):
83
 
    """Register lazy-loaded transport class.
84
 
 
85
 
    When opening a URL with the given scheme, load the module and then
86
 
    instantiate the particular class.  
87
 
 
88
 
    If the module raises DependencyNotPresent when it's imported, it is
89
 
    skipped and another implementation of the protocol is tried.  This is
90
 
    intended to be used when the implementation depends on an external
91
 
    implementation that may not be present.  If any other error is raised, it
92
 
    propagates up and the attempt to open the url fails.
93
 
    """
94
 
    # TODO: If no implementation of a protocol is available because of missing
95
 
    # dependencies, we should perhaps show the message about what dependency
96
 
    # was missing.
97
 
    def _loader(base):
98
 
        mod = __import__(module, globals(), locals(), [classname])
99
 
        klass = getattr(mod, classname)
100
 
        return klass(base)
101
 
    _loader.module = module
102
 
    register_transport(scheme, _loader)
 
60
from bzrlib.trace import (
 
61
    note,
 
62
    mutter,
 
63
    warning,
 
64
    )
 
65
from bzrlib import registry
103
66
 
104
67
 
105
68
def _get_protocol_handlers():
106
69
    """Return a dictionary of {urlprefix: [factory]}"""
107
 
    return _protocol_handlers
 
70
    return transport_list_registry
108
71
 
109
72
 
110
73
def _set_protocol_handlers(new_handlers):
112
75
 
113
76
    WARNING this will remove all build in protocols. Use with care.
114
77
    """
115
 
    global _protocol_handlers
116
 
    _protocol_handlers = new_handlers
 
78
    global transport_list_registry
 
79
    transport_list_registry = new_handlers
117
80
 
118
81
 
119
82
def _clear_protocol_handlers():
120
 
    global _protocol_handlers
121
 
    _protocol_handlers = {}
 
83
    global transport_list_registry
 
84
    transport_list_registry = TransportListRegistry()
122
85
 
123
86
 
124
87
def _get_transport_modules():
125
88
    """Return a list of the modules providing transports."""
126
89
    modules = set()
127
 
    for prefix, factory_list in _protocol_handlers.items():
 
90
    for prefix, factory_list in transport_list_registry.iteritems():
128
91
        for factory in factory_list:
129
 
            if factory.__module__ == "bzrlib.transport":
130
 
                # this is a lazy load transport, because no real ones
131
 
                # are directlry in bzrlib.transport
132
 
                modules.add(factory.module)
 
92
            if hasattr(factory, "_module_name"):
 
93
                modules.add(factory._module_name)
133
94
            else:
134
 
                modules.add(factory.__module__)
 
95
                modules.add(factory._obj.__module__)
 
96
    # Add chroot directly, because there is not handler registered for it.
 
97
    modules.add('bzrlib.transport.chroot')
135
98
    result = list(modules)
136
99
    result.sort()
137
100
    return result
138
101
 
139
102
 
 
103
class TransportListRegistry(registry.Registry):
 
104
    """A registry which simplifies tracking available Transports.
 
105
 
 
106
    A registration of a new protocol requires two step:
 
107
    1) register the prefix with the function register_transport( )
 
108
    2) register the protocol provider with the function
 
109
    register_transport_provider( ) ( and the "lazy" variant )
 
110
 
 
111
    This in needed because:
 
112
    a) a single provider can support multple protcol ( like the ftp
 
113
    privider which supports both the ftp:// and the aftp:// protocols )
 
114
    b) a single protocol can have multiple providers ( like the http://
 
115
    protocol which is supported by both the urllib and pycurl privider )
 
116
    """
 
117
 
 
118
    def register_transport_provider(self, key, obj):
 
119
        self.get(key).insert(0, registry._ObjectGetter(obj))
 
120
 
 
121
    def register_lazy_transport_provider(self, key, module_name, member_name):
 
122
        self.get(key).insert(0, 
 
123
                registry._LazyObjectGetter(module_name, member_name))
 
124
 
 
125
    def register_transport(self, key, help=None, info=None):
 
126
        self.register(key, [], help, info)
 
127
 
 
128
    def set_default_transport(self, key=None):
 
129
        """Return either 'key' or the default key if key is None"""
 
130
        self._default_key = key
 
131
 
 
132
 
 
133
transport_list_registry = TransportListRegistry( )
 
134
 
 
135
 
 
136
def register_transport_proto(prefix, help=None, info=None):
 
137
    transport_list_registry.register_transport(prefix, help, info)
 
138
 
 
139
 
 
140
def register_lazy_transport(prefix, module, classname):
 
141
    if not prefix in transport_list_registry:
 
142
        register_transport_proto(prefix)
 
143
    transport_list_registry.register_lazy_transport_provider(prefix, module, classname)
 
144
 
 
145
 
 
146
def register_transport(prefix, klass, override=DEPRECATED_PARAMETER):
 
147
    if not prefix in transport_list_registry:
 
148
        register_transport_proto(prefix)
 
149
    transport_list_registry.register_transport_provider(prefix, klass)
 
150
 
 
151
 
140
152
def register_urlparse_netloc_protocol(protocol):
141
153
    """Ensure that protocol is setup to be used with urlparse netloc parsing."""
142
154
    if protocol not in urlparse.uses_netloc:
143
155
        urlparse.uses_netloc.append(protocol)
144
156
 
145
157
 
 
158
def unregister_transport(scheme, factory):
 
159
    """Unregister a transport."""
 
160
    l = transport_list_registry.get(scheme)
 
161
    for i in l:
 
162
        o = i.get_obj( )
 
163
        if o == factory:
 
164
            transport_list_registry.get(scheme).remove(i)
 
165
            break
 
166
    if len(l) == 0:
 
167
        transport_list_registry.remove(scheme)
 
168
 
 
169
 
 
170
 
146
171
def split_url(url):
147
172
    # TODO: jam 20060606 urls should only be ascii, or they should raise InvalidURL
148
173
    if isinstance(url, unicode):
201
226
    def close(self):
202
227
        """a no-op - do nothing."""
203
228
 
204
 
    def read(self, count=-1):
 
229
    def _fail(self):
205
230
        """Raise ReadError."""
206
231
        raise errors.ReadError(self._path)
207
232
 
 
233
    def __iter__(self):
 
234
        self._fail()
 
235
 
 
236
    def read(self, count=-1):
 
237
        self._fail()
 
238
 
 
239
    def readlines(self):
 
240
        self._fail()
 
241
 
208
242
 
209
243
class Transport(object):
210
244
    """This class encapsulates methods for retrieving or putting a file
267
301
        """
268
302
        raise NotImplementedError(self.clone)
269
303
 
 
304
    def ensure_base(self):
 
305
        """Ensure that the directory this transport references exists.
 
306
 
 
307
        This will create a directory if it doesn't exist.
 
308
        :return: True if the directory was created, False otherwise.
 
309
        """
 
310
        # The default implementation just uses "Easier to ask for forgiveness
 
311
        # than permission". We attempt to create the directory, and just
 
312
        # suppress a FileExists exception.
 
313
        try:
 
314
            self.mkdir('.')
 
315
        except errors.FileExists:
 
316
            return False
 
317
        else:
 
318
            return True
 
319
 
270
320
    def should_cache(self):
271
321
        """Return True if the data pulled across should be cached locally.
272
322
        """
279
329
        """
280
330
        assert not isinstance(from_file, basestring), \
281
331
            '_pump should only be called on files not %s' % (type(from_file,))
282
 
        pumpfile(from_file, to_file)
 
332
        osutils.pumpfile(from_file, to_file)
283
333
 
284
334
    def _get_total(self, multi):
285
335
        """Try to figure out how many entries are in multi,
340
390
 
341
391
        Examples::
342
392
 
343
 
            >>> t = Transport('/')
344
 
            >>> t._combine_paths('/home/sarah', 'project/foo')
345
 
            '/home/sarah/project/foo'
346
 
            >>> t._combine_paths('/home/sarah', '../../etc')
347
 
            '/etc'
 
393
            t._combine_paths('/home/sarah', 'project/foo')
 
394
                => '/home/sarah/project/foo'
 
395
            t._combine_paths('/home/sarah', '../../etc')
 
396
                => '/etc'
 
397
            t._combine_paths('/home/sarah', '/etc')
 
398
                => '/etc'
348
399
 
349
400
        :param base_path: urlencoded path for the transport root; typically a 
350
401
             URL but need not contain scheme/host/etc.
376
427
            elif p != '':
377
428
                base_parts.append(p)
378
429
        path = '/'.join(base_parts)
 
430
        if not path.startswith('/'):
 
431
            path = '/' + path
379
432
        return path
380
433
 
381
434
    def relpath(self, abspath):
399
452
        This function will only be defined for Transports which have a
400
453
        physical local filesystem representation.
401
454
        """
402
 
        # TODO: jam 20060426 Should this raise NotLocalUrl instead?
403
 
        raise errors.TransportNotPossible('This is not a LocalTransport,'
404
 
            ' so there is no local representation for a path')
 
455
        raise errors.NotLocalUrl(self.abspath(relpath))
 
456
 
405
457
 
406
458
    def has(self, relpath):
407
459
        """Does the file relpath exist?
472
524
    def get_smart_client(self):
473
525
        """Return a smart client for this transport if possible.
474
526
 
 
527
        A smart client doesn't imply the presence of a smart server: it implies
 
528
        that the smart protocol can be tunnelled via this transport.
 
529
 
475
530
        :raises NoSmartServer: if no smart server client is available.
476
531
        """
477
532
        raise errors.NoSmartServer(self.base)
478
533
 
 
534
    def get_smart_medium(self):
 
535
        """Return a smart client medium for this transport if possible.
 
536
 
 
537
        A smart medium doesn't imply the presence of a smart server: it implies
 
538
        that the smart protocol can be tunnelled via this transport.
 
539
 
 
540
        :raises NoSmartMedium: if no smart server medium is available.
 
541
        """
 
542
        raise errors.NoSmartMedium(self)
 
543
 
479
544
    def readv(self, relpath, offsets):
480
545
        """Get parts of the file at the given relative path.
481
546
 
612
677
        :param mode: Create the file with the given mode.
613
678
        :return: None
614
679
        """
615
 
        assert isinstance(bytes, str), \
616
 
            'bytes must be a plain string, not %s' % type(bytes)
 
680
        if not isinstance(bytes, str):
 
681
            raise AssertionError(
 
682
                'bytes must be a plain string, not %s' % type(bytes))
617
683
        return self.put_file(relpath, StringIO(bytes), mode=mode)
618
684
 
619
685
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
634
700
                        create it, and then try again.
635
701
        :param dir_mode: Possible access permissions for new directories.
636
702
        """
637
 
        assert isinstance(bytes, str), \
638
 
            'bytes must be a plain string, not %s' % type(bytes)
 
703
        if not isinstance(bytes, str):
 
704
            raise AssertionError(
 
705
                'bytes must be a plain string, not %s' % type(bytes))
639
706
        self.put_file_non_atomic(relpath, StringIO(bytes), mode=mode,
640
707
                                 create_parent_dir=create_parent_dir,
641
708
                                 dir_mode=dir_mode)
1013
1080
 
1014
1081
    base is either a URL or a directory name.  
1015
1082
    """
1016
 
    # TODO: give a better error if base looks like a url but there's no
1017
 
    # handler for the scheme?
1018
 
    global _protocol_handlers
 
1083
 
1019
1084
    if base is None:
1020
1085
        base = '.'
1021
 
 
1022
1086
    last_err = None
1023
1087
 
1024
1088
    def convert_path_to_url(base, error_str):
1040
1104
        base = convert_path_to_url(base,
1041
1105
            'URLs must be properly escaped (protocol: %s)')
1042
1106
    
1043
 
    for proto, factory_list in _protocol_handlers.iteritems():
 
1107
    for proto, factory_list in transport_list_registry.iteritems():
1044
1108
        if proto is not None and base.startswith(proto):
1045
1109
            t, last_err = _try_transport_factories(base, factory_list)
1046
1110
            if t:
1051
1115
    base = convert_path_to_url(base, 'Unsupported protocol: %s')
1052
1116
 
1053
1117
    # The default handler is the filesystem handler, stored as protocol None
1054
 
    return _try_transport_factories(base, _protocol_handlers[None])[0]
 
1118
    return _try_transport_factories(base,
 
1119
                    transport_list_registry.get(None))[0]
 
1120
                                                   
 
1121
def do_catching_redirections(action, transport, redirected):
 
1122
    """Execute an action with given transport catching redirections.
 
1123
 
 
1124
    This is a facility provided for callers needing to follow redirections
 
1125
    silently. The silence is relative: it is the caller responsability to
 
1126
    inform the user about each redirection or only inform the user of a user
 
1127
    via the exception parameter.
 
1128
 
 
1129
    :param action: A callable, what the caller want to do while catching
 
1130
                  redirections.
 
1131
    :param transport: The initial transport used.
 
1132
    :param redirected: A callable receiving the redirected transport and the 
 
1133
                  RedirectRequested exception.
 
1134
 
 
1135
    :return: Whatever 'action' returns
 
1136
    """
 
1137
    MAX_REDIRECTIONS = 8
 
1138
 
 
1139
    # If a loop occurs, there is little we can do. So we don't try to detect
 
1140
    # them, just getting out if too much redirections occurs. The solution
 
1141
    # is outside: where the loop is defined.
 
1142
    for redirections in range(MAX_REDIRECTIONS):
 
1143
        try:
 
1144
            return action(transport)
 
1145
        except errors.RedirectRequested, e:
 
1146
            redirection_notice = '%s is%s redirected to %s' % (
 
1147
                e.source, e.permanently, e.target)
 
1148
            transport = redirected(transport, e, redirection_notice)
 
1149
    else:
 
1150
        # Loop exited without resolving redirect ? Either the
 
1151
        # user has kept a very very very old reference or a loop
 
1152
        # occurred in the redirections.  Nothing we can cure here:
 
1153
        # tell the user. Note that as the user has been informed
 
1154
        # about each redirection (it is the caller responsibility
 
1155
        # to do that in redirected via the provided
 
1156
        # redirection_notice). The caller may provide more
 
1157
        # information if needed (like what file or directory we
 
1158
        # were trying to act upon when the redirection loop
 
1159
        # occurred).
 
1160
        raise errors.TooManyRedirections
1055
1161
 
1056
1162
 
1057
1163
def _try_transport_factories(base, factory_list):
1058
1164
    last_err = None
1059
1165
    for factory in factory_list:
1060
1166
        try:
1061
 
            return factory(base), None
1062
 
        except DependencyNotPresent, e:
 
1167
            return factory.get_obj()(base), None
 
1168
        except errors.DependencyNotPresent, e:
1063
1169
            mutter("failed to instantiate transport %r for %r: %r" %
1064
1170
                    (factory, base, e))
1065
1171
            last_err = e
1099
1205
        raise NotImplementedError
1100
1206
 
1101
1207
    def get_bogus_url(self):
1102
 
        """Return a url for this protocol, that will fail to connect."""
 
1208
        """Return a url for this protocol, that will fail to connect.
 
1209
        
 
1210
        This may raise NotImplementedError to indicate that this server cannot
 
1211
        provide bogus urls.
 
1212
        """
1103
1213
        raise NotImplementedError
1104
1214
 
1105
1215
 
1106
 
class TransportTestProviderAdapter(object):
1107
 
    """A tool to generate a suite testing all transports for a single test.
1108
 
 
1109
 
    This is done by copying the test once for each transport and injecting
1110
 
    the transport_class and transport_server classes into each copy. Each copy
1111
 
    is also given a new id() to make it easy to identify.
1112
 
    """
1113
 
 
1114
 
    def adapt(self, test):
1115
 
        result = TestSuite()
1116
 
        for klass, server_factory in self._test_permutations():
1117
 
            new_test = deepcopy(test)
1118
 
            new_test.transport_class = klass
1119
 
            new_test.transport_server = server_factory
1120
 
            def make_new_test_id():
1121
 
                new_id = "%s(%s)" % (new_test.id(), server_factory.__name__)
1122
 
                return lambda: new_id
1123
 
            new_test.id = make_new_test_id()
1124
 
            result.addTest(new_test)
1125
 
        return result
1126
 
 
1127
 
    def get_transport_test_permutations(self, module):
1128
 
        """Get the permutations module wants to have tested."""
1129
 
        if getattr(module, 'get_test_permutations', None) is None:
1130
 
            warning("transport module %s doesn't provide get_test_permutations()"
1131
 
                    % module.__name__)
1132
 
            return []
1133
 
        return module.get_test_permutations()
1134
 
 
1135
 
    def _test_permutations(self):
1136
 
        """Return a list of the klass, server_factory pairs to test."""
1137
 
        result = []
1138
 
        for module in _get_transport_modules():
1139
 
            try:
1140
 
                result.extend(self.get_transport_test_permutations(reduce(getattr, 
1141
 
                    (module).split('.')[1:],
1142
 
                     __import__(module))))
1143
 
            except errors.DependencyNotPresent, e:
1144
 
                # Continue even if a dependency prevents us 
1145
 
                # from running this test
1146
 
                pass
1147
 
        return result
1148
 
 
1149
 
 
1150
1216
class TransportLogger(object):
1151
1217
    """Adapt a transport to get clear logging data on api calls.
1152
1218
    
1169
1235
    def readv(self, name, offsets):
1170
1236
        self._calls.append((name, offsets))
1171
1237
        return self._adapted.readv(name, offsets)
1172
 
        
 
1238
 
1173
1239
 
1174
1240
# None is the default transport, for things with no url scheme
1175
 
register_lazy_transport(None, 'bzrlib.transport.local', 'LocalTransport')
 
1241
register_transport_proto('file://',
 
1242
            help="Access using the standard filesystem (default)")
1176
1243
register_lazy_transport('file://', 'bzrlib.transport.local', 'LocalTransport')
 
1244
transport_list_registry.set_default_transport("file://")
 
1245
 
 
1246
register_transport_proto('sftp://',
 
1247
            help="Access using SFTP (most SSH servers provide SFTP).")
1177
1248
register_lazy_transport('sftp://', 'bzrlib.transport.sftp', 'SFTPTransport')
 
1249
# Decorated http transport
 
1250
register_transport_proto('http+urllib://',
 
1251
#                help="Read-only access of branches exported on the web."
 
1252
            )
1178
1253
register_lazy_transport('http+urllib://', 'bzrlib.transport.http._urllib',
1179
1254
                        'HttpTransport_urllib')
 
1255
register_transport_proto('https+urllib://',
 
1256
#                help="Read-only access of branches exported on the web using SSL."
 
1257
            )
1180
1258
register_lazy_transport('https+urllib://', 'bzrlib.transport.http._urllib',
1181
1259
                        'HttpTransport_urllib')
 
1260
register_transport_proto('http+pycurl://',
 
1261
#                help="Read-only access of branches exported on the web."
 
1262
            )
1182
1263
register_lazy_transport('http+pycurl://', 'bzrlib.transport.http._pycurl',
1183
1264
                        'PyCurlTransport')
 
1265
register_transport_proto('https+pycurl://',
 
1266
#                help="Read-only access of branches exported on the web using SSL."
 
1267
            )
1184
1268
register_lazy_transport('https+pycurl://', 'bzrlib.transport.http._pycurl',
1185
1269
                        'PyCurlTransport')
 
1270
# Default http transports (last declared wins (if it can be imported))
 
1271
register_transport_proto('http://',
 
1272
            help="Read-only access of branches exported on the web.")
 
1273
register_transport_proto('https://',
 
1274
            help="Read-only access of branches exported on the web using SSL.")
1186
1275
register_lazy_transport('http://', 'bzrlib.transport.http._urllib',
1187
1276
                        'HttpTransport_urllib')
1188
1277
register_lazy_transport('https://', 'bzrlib.transport.http._urllib',
1189
1278
                        'HttpTransport_urllib')
1190
1279
register_lazy_transport('http://', 'bzrlib.transport.http._pycurl', 'PyCurlTransport')
1191
1280
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl', 'PyCurlTransport')
 
1281
 
 
1282
register_transport_proto('ftp://',
 
1283
            help="Access using passive FTP.")
1192
1284
register_lazy_transport('ftp://', 'bzrlib.transport.ftp', 'FtpTransport')
 
1285
register_transport_proto('aftp://',
 
1286
            help="Access using active FTP.")
1193
1287
register_lazy_transport('aftp://', 'bzrlib.transport.ftp', 'FtpTransport')
 
1288
 
 
1289
register_transport_proto('memory://')
1194
1290
register_lazy_transport('memory://', 'bzrlib.transport.memory', 'MemoryTransport')
 
1291
register_transport_proto('chroot+')
 
1292
 
 
1293
register_transport_proto('readonly+',
 
1294
#              help="This modifier converts any transport to be readonly."
 
1295
            )
1195
1296
register_lazy_transport('readonly+', 'bzrlib.transport.readonly', 'ReadonlyTransportDecorator')
 
1297
register_transport_proto('fakenfs+')
1196
1298
register_lazy_transport('fakenfs+', 'bzrlib.transport.fakenfs', 'FakeNFSTransportDecorator')
 
1299
register_transport_proto('vfat+')
1197
1300
register_lazy_transport('vfat+',
1198
1301
                        'bzrlib.transport.fakevfat',
1199
1302
                        'FakeVFATTransportDecorator')
 
1303
register_transport_proto('bzr://',
 
1304
            help="Fast access using the Bazaar smart server.")
 
1305
 
1200
1306
register_lazy_transport('bzr://',
1201
 
                        'bzrlib.transport.smart',
1202
 
                        'SmartTCPTransport')
 
1307
                        'bzrlib.transport.remote',
 
1308
                        'RemoteTCPTransport')
 
1309
register_transport_proto('bzr+http://',
 
1310
#                help="Fast access using the Bazaar smart server over HTTP."
 
1311
             )
 
1312
register_lazy_transport('bzr+http://',
 
1313
                        'bzrlib.transport.remote',
 
1314
                        'RemoteHTTPTransport')
 
1315
register_transport_proto('bzr+ssh://',
 
1316
            help="Fast access using the Bazaar smart server over SSH.")
1203
1317
register_lazy_transport('bzr+ssh://',
1204
 
                        'bzrlib.transport.smart',
1205
 
                        'SmartSSHTransport')
 
1318
                        'bzrlib.transport.remote',
 
1319
                        'RemoteSSHTransport')