~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/__init__.py

  • Committer: Vincent Ladeuil
  • Date: 2007-06-06 13:52:02 UTC
  • mto: (2485.8.44 bzr.connection.sharing)
  • mto: This revision was merged to the branch mainline in revision 2646.
  • Revision ID: v.ladeuil+lp@free.fr-20070606135202-mqhxcv6z57uce434
Fix merge multiple connections. Test suite *not* passing (sftp
refactoring pending but unrelated to merge).

* bzrlib/builtins.py:
(cmd_merge.run): Fix the multiple connections bug by reusing the
tramsport used to check for a bundle and keep all other used
transports in possible_transports.
(_merge_helper): Add a possible_transports parameter for
reuse.

* bzrlib/transport/__init__.py:
(Transport._reuse_for): By default, Transports are not reusable.
(ConnectedTransport._reuse_for): ConnectedTransports are reusable
under certain conditions.
(_urlRE): Fix misleading group name.
(_try_transport_factories): Moved after get_transport (another use
case for moved lines). The do_catching_redirections was
incorrectly inserted between get_transport and
_try_transport_factories.

* bzrlib/tests/test_transport.py:
(TestReusedTransports.test_reuse_same_transport)
(TestReusedTransports.test_don_t_reuse_different_transport): Add
more tests.

* bzrlib/merge.py:
(_get_tree, Merger.set_other): Add a possible_transports parameter
for reuse.

* bzrlib/bzrdir.py:
(BzrDir.open_containing): Add a possible_transports parameter for
reuse.

* bzrlib/branch.py:
(Branch.open_containing): Add a possible_transports parameter for
reuse.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1028
1028
        # several questions about the transport.
1029
1029
        return False
1030
1030
 
 
1031
    def _reuse_for(self, other_base):
 
1032
        return None
1031
1033
 
1032
1034
class ConnectedTransport(Transport):
1033
1035
    """A transport connected to a remote server.
1242
1244
        (connection, credentials) = self._connection[0]
1243
1245
        return self._connection[0][1]
1244
1246
 
 
1247
    def _reuse_for(self, other_base):
 
1248
        """Returns a transport sharing the same connection if possible.
 
1249
 
 
1250
        Note: we share the connection if the expected credentials are the
 
1251
        same: (host, port, user). Some protocols may disagree and redefine the
 
1252
        criteria in daughter classes.
 
1253
 
 
1254
        Note: we don't compare the passwords here because other_base may have
 
1255
        been obtained from an existing transport.base which do not mention the
 
1256
        password.
 
1257
 
 
1258
        :param other_base: the URL we want to share the connection with.
 
1259
 
 
1260
        :return: A new transport or None if the connection cannot be shared.
 
1261
        """
 
1262
        (scheme, user, password, host, port, path) = self._split_url(other_base)
 
1263
        transport = None
 
1264
        # Don't compare passwords, they may be absent from other_base
 
1265
        if (scheme,
 
1266
            user,
 
1267
            host, port) == (self._scheme,
 
1268
                            self._user,
 
1269
                            self._host, self._port):
 
1270
            if not path.endswith('/'):
 
1271
                # This normally occurs at __init__ time, but it's easier to do
 
1272
                # it now to avoid positives (creating two transports for the
 
1273
                # same base).
 
1274
                path += '/'
 
1275
            if self._path  == path:
 
1276
                # shortcut, it's really the same transport
 
1277
                return self
 
1278
            # We don't call clone here because the intent is different: we
 
1279
            # build a new transport on a different base (which may be totally
 
1280
            # unrelated) but we share the connection.
 
1281
            transport = self.__class__(other_base, self)
 
1282
        return transport
 
1283
 
 
1284
 
1245
1285
 
1246
1286
# jam 20060426 For compatibility we copy the functions here
1247
1287
# TODO: The should be marked as deprecated
1248
1288
urlescape = urlutils.escape
1249
1289
urlunescape = urlutils.unescape
1250
 
_urlRE = re.compile(r'^(?P<proto>[^:/\\]+)://(?P<path>.*)$')
 
1290
# We try to recognize an url lazily (ignoring user, password, etc)
 
1291
_urlRE = re.compile(r'^(?P<proto>[^:/\\]+)://(?P<rest>.*)$')
1251
1292
 
1252
1293
def get_transport(base, possible_transports=None):
1253
1294
    """Open a transport to access a URL or directory.
1254
1295
 
1255
1296
    :param base: either a URL or a directory name.
1256
 
    :param transports: optional reusable transports list.
 
1297
 
 
1298
    :param transports: optional reusable transports list. If not None, created
 
1299
    transports will be added to the list.
 
1300
 
 
1301
    :return: A new transport optionally sharing its connection with one of
 
1302
    possible_transports.
1257
1303
    """
1258
1304
    if base is None:
1259
1305
        base = '.'
1281
1327
    transport = None
1282
1328
    if possible_transports:
1283
1329
        for t in possible_transports:
1284
 
            if t.base == base:
1285
 
                transport = t
1286
 
                break
1287
 
    if transport is None:
1288
 
        for proto, factory_list in transport_list_registry.iteritems():
1289
 
            if proto is not None and base.startswith(proto):
1290
 
                transport, last_err = _try_transport_factories(base,
1291
 
                                                               factory_list)
1292
 
                if transport:
1293
 
                    break
1294
 
    if transport is None:
1295
 
        # We tried all the different protocols, now try one last
1296
 
        # time as a local protocol
1297
 
        base = convert_path_to_url(base, 'Unsupported protocol: %s')
1298
 
 
1299
 
        # The default handler is the filesystem handler, stored
1300
 
        # as protocol None
1301
 
        factory_list = transport_list_registry.get(None)
1302
 
        transport, last_err = _try_transport_factories(base, factory_list)
 
1330
            t_same_connection = t._reuse_for(base)
 
1331
            if t_same_connection is not None:
 
1332
                # Add only new transports
 
1333
                if t_same_connection not in possible_transports:
 
1334
                    possible_transports.append(t_same_connection)
 
1335
                return t_same_connection
 
1336
 
 
1337
    for proto, factory_list in transport_list_registry.iteritems():
 
1338
        if proto is not None and base.startswith(proto):
 
1339
            transport, last_err = _try_transport_factories(base, factory_list)
 
1340
            if transport:
 
1341
                if possible_transports:
 
1342
                    assert transport not in possible_transports
 
1343
                    possible_transports.append(transport)
 
1344
                return transport
 
1345
 
 
1346
    # We tried all the different protocols, now try one last
 
1347
    # time as a local protocol
 
1348
    base = convert_path_to_url(base, 'Unsupported protocol: %s')
 
1349
 
 
1350
    # The default handler is the filesystem handler, stored
 
1351
    # as protocol None
 
1352
    factory_list = transport_list_registry.get(None)
 
1353
    transport, last_err = _try_transport_factories(base, factory_list)
 
1354
 
1303
1355
    return transport
1304
1356
 
1305
1357
 
 
1358
def _try_transport_factories(base, factory_list):
 
1359
    last_err = None
 
1360
    for factory in factory_list:
 
1361
        try:
 
1362
            return factory.get_obj()(base), None
 
1363
        except errors.DependencyNotPresent, e:
 
1364
            mutter("failed to instantiate transport %r for %r: %r" %
 
1365
                    (factory, base, e))
 
1366
            last_err = e
 
1367
            continue
 
1368
    return None, last_err
 
1369
 
 
1370
 
1306
1371
def do_catching_redirections(action, transport, redirected):
1307
1372
    """Execute an action with given transport catching redirections.
1308
1373
 
1345
1410
        raise errors.TooManyRedirections
1346
1411
 
1347
1412
 
1348
 
def _try_transport_factories(base, factory_list):
1349
 
    last_err = None
1350
 
    for factory in factory_list:
1351
 
        try:
1352
 
            return factory.get_obj()(base), None
1353
 
        except errors.DependencyNotPresent, e:
1354
 
            mutter("failed to instantiate transport %r for %r: %r" %
1355
 
                    (factory, base, e))
1356
 
            last_err = e
1357
 
            continue
1358
 
    return None, last_err
1359
 
 
1360
 
 
1361
1413
class Server(object):
1362
1414
    """A Transport Server.
1363
1415