~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/remote.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-11-09 03:13:16 UTC
  • mfrom: (1551.18.23 Aaron's mergeable stuff)
  • Revision ID: pqm@pqm.ubuntu.com-20071109031316-n814a39wmtxvybyw
Ensure that setting attributes on ScopeReplacer objects works

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""RemoteTransport client for the smart-server.
18
18
 
20
20
imported from bzrlib.smart.
21
21
"""
22
22
 
23
 
from __future__ import absolute_import
24
 
 
25
23
__all__ = ['RemoteTransport', 'RemoteTCPTransport', 'RemoteSSHTransport']
26
24
 
27
25
from cStringIO import StringIO
 
26
import urllib
 
27
import urlparse
28
28
 
29
29
from bzrlib import (
30
30
    config,
31
31
    debug,
32
32
    errors,
33
 
    remote,
34
33
    trace,
35
34
    transport,
36
35
    urlutils,
37
36
    )
38
 
from bzrlib.smart import client, medium
 
37
from bzrlib.smart import client, medium, protocol
 
38
 
 
39
 
 
40
# Port 4155 is the default port for bzr://, registered with IANA.
 
41
BZR_DEFAULT_INTERFACE = '0.0.0.0'
 
42
BZR_DEFAULT_PORT = 4155
39
43
 
40
44
 
41
45
class _SmartStat(object):
53
57
 
54
58
    The connection has a notion of the current directory to which it's
55
59
    connected; this is incorporated in filenames passed to the server.
56
 
 
57
 
    This supports some higher-level RPC operations and can also be treated
 
60
    
 
61
    This supports some higher-level RPC operations and can also be treated 
58
62
    like a Transport to do file-like operations.
59
63
 
60
64
    The connection can be made over a tcp socket, an ssh pipe or a series of
62
66
    RemoteTCPTransport, etc.
63
67
    """
64
68
 
65
 
    # When making a readv request, cap it at requesting 5MB of data
66
 
    _max_readv_bytes = 5*1024*1024
67
 
 
68
69
    # IMPORTANT FOR IMPLEMENTORS: RemoteTransport MUST NOT be given encoding
69
70
    # responsibilities: Put those on SmartClient or similar. This is vital for
70
71
    # the ability to support multiple versions of the smart protocol over time:
71
 
    # RemoteTransport is an adapter from the Transport object model to the
 
72
    # RemoteTransport is an adapter from the Transport object model to the 
72
73
    # SmartClient model, not an encoder.
73
74
 
74
75
    # FIXME: the medium parameter should be private, only the tests requires
82
83
            one is being cloned from.  Attributes such as the medium will
83
84
            be reused.
84
85
 
85
 
        :param medium: The medium to use for this RemoteTransport.  If None,
86
 
            the medium from the _from_transport is shared.  If both this
87
 
            and _from_transport are None, a new medium will be built.
88
 
            _from_transport and medium cannot both be specified.
 
86
        :param medium: The medium to use for this RemoteTransport. This must be
 
87
            supplied if _from_transport is None.
89
88
 
90
89
        :param _client: Override the _SmartClient used by this transport.  This
91
90
            should only be used for testing purposes; normally this is
92
91
            determined from the medium.
93
92
        """
94
 
        super(RemoteTransport, self).__init__(
95
 
            url, _from_transport=_from_transport)
 
93
        super(RemoteTransport, self).__init__(url,
 
94
                                              _from_transport=_from_transport)
96
95
 
97
96
        # The medium is the connection, except when we need to share it with
98
97
        # other objects (RemoteBzrDir, RemoteRepository etc). In these cases
99
98
        # what we want to share is really the shared connection.
100
99
 
101
 
        if (_from_transport is not None
102
 
            and isinstance(_from_transport, RemoteTransport)):
103
 
            _client = _from_transport._client
104
 
        elif _from_transport is None:
 
100
        if _from_transport is None:
105
101
            # If no _from_transport is specified, we need to intialize the
106
102
            # shared medium.
107
103
            credentials = None
111
107
                    trace.mutter('hpss: Built a new medium: %s',
112
108
                                 medium.__class__.__name__)
113
109
            self._shared_connection = transport._SharedConnection(medium,
114
 
                                                                  credentials,
115
 
                                                                  self.base)
116
 
        elif medium is None:
117
 
            # No medium was specified, so share the medium from the
118
 
            # _from_transport.
119
 
            medium = self._shared_connection.connection
120
 
        else:
121
 
            raise AssertionError(
122
 
                "Both _from_transport (%r) and medium (%r) passed to "
123
 
                "RemoteTransport.__init__, but these parameters are mutally "
124
 
                "exclusive." % (_from_transport, medium))
 
110
                                                                  credentials)
125
111
 
126
112
        if _client is None:
127
 
            self._client = client._SmartClient(medium)
 
113
            self._client = client._SmartClient(self.get_shared_medium())
128
114
        else:
129
115
            self._client = _client
130
116
 
137
123
        # No credentials
138
124
        return None, None
139
125
 
140
 
    def _report_activity(self, bytes, direction):
141
 
        """See Transport._report_activity.
142
 
 
143
 
        Does nothing; the smart medium will report activity triggered by a
144
 
        RemoteTransport.
145
 
        """
146
 
        pass
147
 
 
148
126
    def is_readonly(self):
149
127
        """Smart server transport can do read/write file operations."""
150
 
        try:
151
 
            resp = self._call2('Transport.is_readonly')
152
 
        except errors.UnknownSmartMethod:
 
128
        resp = self._call2('Transport.is_readonly')
 
129
        if resp == ('yes', ):
 
130
            return True
 
131
        elif resp == ('no', ):
 
132
            return False
 
133
        elif (resp == ('error', "Generic bzr smart protocol error: "
 
134
                                "bad request 'Transport.is_readonly'") or
 
135
              resp == ('error', "Generic bzr smart protocol error: "
 
136
                                "bad request u'Transport.is_readonly'")):
153
137
            # XXX: nasty hack: servers before 0.16 don't have a
154
138
            # 'Transport.is_readonly' verb, so we do what clients before 0.16
155
139
            # did: assume False.
156
140
            return False
157
 
        if resp == ('yes', ):
158
 
            return True
159
 
        elif resp == ('no', ):
160
 
            return False
161
141
        else:
162
 
            raise errors.UnexpectedSmartServerResponse(resp)
 
142
            self._translate_error(resp)
 
143
        raise errors.UnexpectedSmartServerResponse(resp)
163
144
 
164
145
    def get_smart_client(self):
165
146
        return self._get_connection()
167
148
    def get_smart_medium(self):
168
149
        return self._get_connection()
169
150
 
 
151
    def get_shared_medium(self):
 
152
        return self._get_shared_connection()
 
153
 
170
154
    def _remote_path(self, relpath):
171
155
        """Returns the Unicode version of the absolute path for relpath."""
172
 
        return urlutils.URL._combine_paths(self._parsed_url.path, relpath)
 
156
        return self._combine_paths(self._path, relpath)
173
157
 
174
158
    def _call(self, method, *args):
175
159
        resp = self._call2(method, *args)
176
 
        self._ensure_ok(resp)
 
160
        self._translate_error(resp)
177
161
 
178
162
    def _call2(self, method, *args):
179
163
        """Call a method on the remote server."""
180
 
        try:
181
 
            return self._client.call(method, *args)
182
 
        except errors.ErrorFromSmartServer, err:
183
 
            # The first argument, if present, is always a path.
184
 
            if args:
185
 
                context = {'relpath': args[0]}
186
 
            else:
187
 
                context = {}
188
 
            self._translate_error(err, **context)
 
164
        return self._client.call(method, *args)
189
165
 
190
166
    def _call_with_body_bytes(self, method, args, body):
191
167
        """Call a method on the remote server with body bytes."""
192
 
        try:
193
 
            return self._client.call_with_body_bytes(method, args, body)
194
 
        except errors.ErrorFromSmartServer, err:
195
 
            # The first argument, if present, is always a path.
196
 
            if args:
197
 
                context = {'relpath': args[0]}
198
 
            else:
199
 
                context = {}
200
 
            self._translate_error(err, **context)
 
168
        return self._client.call_with_body_bytes(method, args, body)
201
169
 
202
170
    def has(self, relpath):
203
171
        """Indicate whether a remote file of the given name exists or not.
210
178
        elif resp == ('no', ):
211
179
            return False
212
180
        else:
213
 
            raise errors.UnexpectedSmartServerResponse(resp)
 
181
            self._translate_error(resp)
214
182
 
215
183
    def get(self, relpath):
216
184
        """Return file-like object reading the contents of a remote file.
217
 
 
 
185
        
218
186
        :see: Transport.get_bytes()/get_file()
219
187
        """
220
188
        return StringIO(self.get_bytes(relpath))
221
189
 
222
190
    def get_bytes(self, relpath):
223
191
        remote = self._remote_path(relpath)
224
 
        try:
225
 
            resp, response_handler = self._client.call_expecting_body('get', remote)
226
 
        except errors.ErrorFromSmartServer, err:
227
 
            self._translate_error(err, relpath)
 
192
        request = self.get_smart_medium().get_request()
 
193
        smart_protocol = protocol.SmartClientRequestProtocolOne(request)
 
194
        smart_protocol.call('get', remote)
 
195
        resp = smart_protocol.read_response_tuple(True)
228
196
        if resp != ('ok', ):
229
 
            response_handler.cancel_read_body()
230
 
            raise errors.UnexpectedSmartServerResponse(resp)
231
 
        return response_handler.read_body_bytes()
 
197
            smart_protocol.cancel_read_body()
 
198
            self._translate_error(resp, relpath)
 
199
        return smart_protocol.read_body_bytes()
232
200
 
233
201
    def _serialise_optional_mode(self, mode):
234
202
        if mode is None:
239
207
    def mkdir(self, relpath, mode=None):
240
208
        resp = self._call2('mkdir', self._remote_path(relpath),
241
209
            self._serialise_optional_mode(mode))
 
210
        self._translate_error(resp)
242
211
 
243
212
    def open_write_stream(self, relpath, mode=None):
244
213
        """See Transport.open_write_stream."""
260
229
        resp = self._call_with_body_bytes('put',
261
230
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
262
231
            upload_contents)
263
 
        self._ensure_ok(resp)
 
232
        self._translate_error(resp)
264
233
        return len(upload_contents)
265
234
 
266
235
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
277
246
            (self._remote_path(relpath), self._serialise_optional_mode(mode),
278
247
             create_parent_str, self._serialise_optional_mode(dir_mode)),
279
248
            bytes)
280
 
        self._ensure_ok(resp)
 
249
        self._translate_error(resp)
281
250
 
282
251
    def put_file(self, relpath, upload_file, mode=None):
283
252
        # its not ideal to seek back, but currently put_non_atomic_file depends
299
268
 
300
269
    def append_file(self, relpath, from_file, mode=None):
301
270
        return self.append_bytes(relpath, from_file.read(), mode)
302
 
 
 
271
        
303
272
    def append_bytes(self, relpath, bytes, mode=None):
304
273
        resp = self._call_with_body_bytes(
305
274
            'append',
307
276
            bytes)
308
277
        if resp[0] == 'appended':
309
278
            return int(resp[1])
310
 
        raise errors.UnexpectedSmartServerResponse(resp)
 
279
        self._translate_error(resp)
311
280
 
312
281
    def delete(self, relpath):
313
282
        resp = self._call2('delete', self._remote_path(relpath))
314
 
        self._ensure_ok(resp)
 
283
        self._translate_error(resp)
315
284
 
316
285
    def external_url(self):
317
286
        """See bzrlib.transport.Transport.external_url."""
318
287
        # the external path for RemoteTransports is the base
319
288
        return self.base
320
289
 
321
 
    def recommended_page_size(self):
322
 
        """Return the recommended page size for this transport."""
323
 
        return 64 * 1024
324
 
 
325
290
    def _readv(self, relpath, offsets):
326
291
        if not offsets:
327
292
            return
329
294
        offsets = list(offsets)
330
295
 
331
296
        sorted_offsets = sorted(offsets)
 
297
        # turn the list of offsets into a stack
 
298
        offset_stack = iter(offsets)
 
299
        cur_offset_and_size = offset_stack.next()
332
300
        coalesced = list(self._coalesce_offsets(sorted_offsets,
333
301
                               limit=self._max_readv_combine,
334
 
                               fudge_factor=self._bytes_to_read_before_seek,
335
 
                               max_size=self._max_readv_bytes))
336
 
 
337
 
        # now that we've coallesced things, avoid making enormous requests
338
 
        requests = []
339
 
        cur_request = []
340
 
        cur_len = 0
341
 
        for c in coalesced:
342
 
            if c.length + cur_len > self._max_readv_bytes:
343
 
                requests.append(cur_request)
344
 
                cur_request = [c]
345
 
                cur_len = c.length
346
 
                continue
347
 
            cur_request.append(c)
348
 
            cur_len += c.length
349
 
        if cur_request:
350
 
            requests.append(cur_request)
351
 
        if 'hpss' in debug.debug_flags:
352
 
            trace.mutter('%s.readv %s offsets => %s coalesced'
353
 
                         ' => %s requests (%s)',
354
 
                         self.__class__.__name__, len(offsets), len(coalesced),
355
 
                         len(requests), sum(map(len, requests)))
 
302
                               fudge_factor=self._bytes_to_read_before_seek))
 
303
 
 
304
        request = self.get_smart_medium().get_request()
 
305
        smart_protocol = protocol.SmartClientRequestProtocolOne(request)
 
306
        smart_protocol.call_with_body_readv_array(
 
307
            ('readv', self._remote_path(relpath)),
 
308
            [(c.start, c.length) for c in coalesced])
 
309
        resp = smart_protocol.read_response_tuple(True)
 
310
 
 
311
        if resp[0] != 'readv':
 
312
            # This should raise an exception
 
313
            smart_protocol.cancel_read_body()
 
314
            self._translate_error(resp)
 
315
            return
 
316
 
 
317
        # FIXME: this should know how many bytes are needed, for clarity.
 
318
        data = smart_protocol.read_body_bytes()
356
319
        # Cache the results, but only until they have been fulfilled
357
320
        data_map = {}
358
 
        # turn the list of offsets into a single stack to iterate
359
 
        offset_stack = iter(offsets)
360
 
        # using a list so it can be modified when passing down and coming back
361
 
        next_offset = [offset_stack.next()]
362
 
        for cur_request in requests:
363
 
            try:
364
 
                result = self._client.call_with_body_readv_array(
365
 
                    ('readv', self._remote_path(relpath),),
366
 
                    [(c.start, c.length) for c in cur_request])
367
 
                resp, response_handler = result
368
 
            except errors.ErrorFromSmartServer, err:
369
 
                self._translate_error(err, relpath)
370
 
 
371
 
            if resp[0] != 'readv':
372
 
                # This should raise an exception
373
 
                response_handler.cancel_read_body()
374
 
                raise errors.UnexpectedSmartServerResponse(resp)
375
 
 
376
 
            for res in self._handle_response(offset_stack, cur_request,
377
 
                                             response_handler,
378
 
                                             data_map,
379
 
                                             next_offset):
380
 
                yield res
381
 
 
382
 
    def _handle_response(self, offset_stack, coalesced, response_handler,
383
 
                         data_map, next_offset):
384
 
        cur_offset_and_size = next_offset[0]
385
 
        # FIXME: this should know how many bytes are needed, for clarity.
386
 
        data = response_handler.read_body_bytes()
387
 
        data_offset = 0
388
321
        for c_offset in coalesced:
389
322
            if len(data) < c_offset.length:
390
323
                raise errors.ShortReadvError(relpath, c_offset.start,
391
324
                            c_offset.length, actual=len(data))
392
325
            for suboffset, subsize in c_offset.ranges:
393
326
                key = (c_offset.start+suboffset, subsize)
394
 
                this_data = data[data_offset+suboffset:
395
 
                                 data_offset+suboffset+subsize]
396
 
                # Special case when the data is in-order, rather than packing
397
 
                # into a map and then back out again. Benchmarking shows that
398
 
                # this has 100% hit rate, but leave in the data_map work just
399
 
                # in case.
400
 
                # TODO: Could we get away with using buffer() to avoid the
401
 
                #       memory copy?  Callers would need to realize they may
402
 
                #       not have a real string.
403
 
                if key == cur_offset_and_size:
404
 
                    yield cur_offset_and_size[0], this_data
405
 
                    cur_offset_and_size = next_offset[0] = offset_stack.next()
406
 
                else:
407
 
                    data_map[key] = this_data
408
 
            data_offset += c_offset.length
 
327
                data_map[key] = data[suboffset:suboffset+subsize]
 
328
            data = data[c_offset.length:]
409
329
 
410
330
            # Now that we've read some data, see if we can yield anything back
411
331
            while cur_offset_and_size in data_map:
412
332
                this_data = data_map.pop(cur_offset_and_size)
413
333
                yield cur_offset_and_size[0], this_data
414
 
                cur_offset_and_size = next_offset[0] = offset_stack.next()
 
334
                cur_offset_and_size = offset_stack.next()
415
335
 
416
336
    def rename(self, rel_from, rel_to):
417
337
        self._call('rename',
426
346
    def rmdir(self, relpath):
427
347
        resp = self._call('rmdir', self._remote_path(relpath))
428
348
 
429
 
    def _ensure_ok(self, resp):
430
 
        if resp[0] != 'ok':
431
 
            raise errors.UnexpectedSmartServerResponse(resp)
432
 
 
433
 
    def _translate_error(self, err, relpath=None):
434
 
        remote._translate_error(err, path=relpath)
 
349
    def _translate_error(self, resp, orig_path=None):
 
350
        """Raise an exception from a response"""
 
351
        if resp is None:
 
352
            what = None
 
353
        else:
 
354
            what = resp[0]
 
355
        if what == 'ok':
 
356
            return
 
357
        elif what == 'NoSuchFile':
 
358
            if orig_path is not None:
 
359
                error_path = orig_path
 
360
            else:
 
361
                error_path = resp[1]
 
362
            raise errors.NoSuchFile(error_path)
 
363
        elif what == 'error':
 
364
            raise errors.SmartProtocolError(unicode(resp[1]))
 
365
        elif what == 'FileExists':
 
366
            raise errors.FileExists(resp[1])
 
367
        elif what == 'DirectoryNotEmpty':
 
368
            raise errors.DirectoryNotEmpty(resp[1])
 
369
        elif what == 'ShortReadvError':
 
370
            raise errors.ShortReadvError(resp[1], int(resp[2]),
 
371
                                         int(resp[3]), int(resp[4]))
 
372
        elif what in ('UnicodeEncodeError', 'UnicodeDecodeError'):
 
373
            encoding = str(resp[1]) # encoding must always be a string
 
374
            val = resp[2]
 
375
            start = int(resp[3])
 
376
            end = int(resp[4])
 
377
            reason = str(resp[5]) # reason must always be a string
 
378
            if val.startswith('u:'):
 
379
                val = val[2:].decode('utf-8')
 
380
            elif val.startswith('s:'):
 
381
                val = val[2:].decode('base64')
 
382
            if what == 'UnicodeDecodeError':
 
383
                raise UnicodeDecodeError(encoding, val, start, end, reason)
 
384
            elif what == 'UnicodeEncodeError':
 
385
                raise UnicodeEncodeError(encoding, val, start, end, reason)
 
386
        elif what == "ReadOnlyError":
 
387
            raise errors.TransportNotPossible('readonly transport')
 
388
        elif what == "ReadError":
 
389
            if orig_path is not None:
 
390
                error_path = orig_path
 
391
            else:
 
392
                error_path = resp[1]
 
393
            raise errors.ReadError(error_path)
 
394
        else:
 
395
            raise errors.SmartProtocolError('unexpected smart server error: %r' % (resp,))
435
396
 
436
397
    def disconnect(self):
437
 
        m = self.get_smart_medium()
438
 
        if m is not None:
439
 
            m.disconnect()
 
398
        self.get_smart_medium().disconnect()
 
399
 
 
400
    def delete_tree(self, relpath):
 
401
        raise errors.TransportNotPossible('readonly transport')
440
402
 
441
403
    def stat(self, relpath):
442
404
        resp = self._call2('stat', self._remote_path(relpath))
443
405
        if resp[0] == 'stat':
444
406
            return _SmartStat(int(resp[1]), int(resp[2], 8))
445
 
        raise errors.UnexpectedSmartServerResponse(resp)
 
407
        else:
 
408
            self._translate_error(resp)
446
409
 
447
410
    ## def lock_read(self, relpath):
448
411
    ##     """Lock the given file for shared (read) access.
464
427
        resp = self._call2('list_dir', self._remote_path(relpath))
465
428
        if resp[0] == 'names':
466
429
            return [name.encode('ascii') for name in resp[1:]]
467
 
        raise errors.UnexpectedSmartServerResponse(resp)
 
430
        else:
 
431
            self._translate_error(resp)
468
432
 
469
433
    def iter_files_recursive(self):
470
434
        resp = self._call2('iter_files_recursive', self._remote_path(''))
471
435
        if resp[0] == 'names':
472
436
            return resp[1:]
473
 
        raise errors.UnexpectedSmartServerResponse(resp)
 
437
        else:
 
438
            self._translate_error(resp)
474
439
 
475
440
 
476
441
class RemoteTCPTransport(RemoteTransport):
477
442
    """Connection to smart server over plain tcp.
478
 
 
 
443
    
479
444
    This is essentially just a factory to get 'RemoteTransport(url,
480
445
        SmartTCPClientMedium).
481
446
    """
482
447
 
483
448
    def _build_medium(self):
484
 
        client_medium = medium.SmartTCPClientMedium(
485
 
            self._parsed_url.host, self._parsed_url.port, self.base)
486
 
        return client_medium, None
487
 
 
488
 
 
489
 
class RemoteTCPTransportV2Only(RemoteTransport):
490
 
    """Connection to smart server over plain tcp with the client hard-coded to
491
 
    assume protocol v2 and remote server version <= 1.6.
492
 
 
493
 
    This should only be used for testing.
494
 
    """
495
 
 
496
 
    def _build_medium(self):
497
 
        client_medium = medium.SmartTCPClientMedium(
498
 
            self._parsed_url.host, self._parsed_url.port, self.base)
499
 
        client_medium._protocol_version = 2
500
 
        client_medium._remember_remote_is_before((1, 6))
501
 
        return client_medium, None
 
449
        assert self.base.startswith('bzr://')
 
450
        return medium.SmartTCPClientMedium(self._host, self._port), None
502
451
 
503
452
 
504
453
class RemoteSSHTransport(RemoteTransport):
509
458
    """
510
459
 
511
460
    def _build_medium(self):
 
461
        assert self.base.startswith('bzr+ssh://')
 
462
        # ssh will prompt the user for a password if needed and if none is
 
463
        # provided but it will not give it back, so no credentials can be
 
464
        # stored.
512
465
        location_config = config.LocationConfig(self.base)
513
466
        bzr_remote_path = location_config.get_bzr_remote_path()
514
 
        user = self._parsed_url.user
515
 
        if user is None:
516
 
            auth = config.AuthenticationConfig()
517
 
            user = auth.get_user('ssh', self._parsed_url.host,
518
 
                self._parsed_url.port)
519
 
        ssh_params = medium.SSHParams(self._parsed_url.host,
520
 
                self._parsed_url.port, user, self._parsed_url.password,
521
 
                bzr_remote_path)
522
 
        client_medium = medium.SmartSSHClientMedium(self.base, ssh_params)
523
 
        return client_medium, (user, self._parsed_url.password)
 
467
        return medium.SmartSSHClientMedium(self._host, self._port,
 
468
            self._user, self._password, bzr_remote_path=bzr_remote_path), None
524
469
 
525
470
 
526
471
class RemoteHTTPTransport(RemoteTransport):
527
472
    """Just a way to connect between a bzr+http:// url and http://.
528
 
 
 
473
    
529
474
    This connection operates slightly differently than the RemoteSSHTransport.
530
475
    It uses a plain http:// transport underneath, which defines what remote
531
476
    .bzr/smart URL we are connected to. From there, all paths that are sent are
535
480
    """
536
481
 
537
482
    def __init__(self, base, _from_transport=None, http_transport=None):
 
483
        assert ( base.startswith('bzr+http://') or base.startswith('bzr+https://') )
 
484
 
538
485
        if http_transport is None:
539
486
            # FIXME: the password may be lost here because it appears in the
540
487
            # url only for an intial construction (when the url came from the
541
488
            # command-line).
542
489
            http_url = base[len('bzr+'):]
543
 
            self._http_transport = transport.get_transport_from_url(http_url)
 
490
            self._http_transport = transport.get_transport(http_url)
544
491
        else:
545
492
            self._http_transport = http_transport
546
493
        super(RemoteHTTPTransport, self).__init__(
554
501
        """After connecting, HTTP Transport only deals in relative URLs."""
555
502
        # Adjust the relpath based on which URL this smart transport is
556
503
        # connected to.
557
 
        http_base = urlutils.normalize_url(self.get_smart_medium().base)
 
504
        http_base = urlutils.normalize_url(self._http_transport.base)
558
505
        url = urlutils.join(self.base[len('bzr+'):], relpath)
559
506
        url = urlutils.normalize_url(url)
560
507
        return urlutils.relative_url(http_base, url)
571
518
        smart requests may be different).  This is so that the server doesn't
572
519
        have to handle .bzr/smart requests at arbitrary places inside .bzr
573
520
        directories, just at the initial URL the user uses.
 
521
 
 
522
        The exception is parent paths (i.e. relative_url of "..").
574
523
        """
575
524
        if relative_url:
576
525
            abs_url = self.abspath(relative_url)
577
526
        else:
578
527
            abs_url = self.base
 
528
        # We either use the exact same http_transport (for child locations), or
 
529
        # a clone of the underlying http_transport (for parent locations).  This
 
530
        # means we share the connection.
 
531
        norm_base = urlutils.normalize_url(self.base)
 
532
        norm_abs_url = urlutils.normalize_url(abs_url)
 
533
        normalized_rel_url = urlutils.relative_url(norm_base, norm_abs_url)
 
534
        if normalized_rel_url == ".." or normalized_rel_url.startswith("../"):
 
535
            http_transport = self._http_transport.clone(normalized_rel_url)
 
536
        else:
 
537
            http_transport = self._http_transport
579
538
        return RemoteHTTPTransport(abs_url,
580
539
                                   _from_transport=self,
581
 
                                   http_transport=self._http_transport)
582
 
 
583
 
    def _redirected_to(self, source, target):
584
 
        """See transport._redirected_to"""
585
 
        redirected = self._http_transport._redirected_to(source, target)
586
 
        if (redirected is not None
587
 
            and isinstance(redirected, type(self._http_transport))):
588
 
            return RemoteHTTPTransport('bzr+' + redirected.external_url(),
589
 
                                       http_transport=redirected)
590
 
        else:
591
 
            # Either None or a transport for a different protocol
592
 
            return redirected
593
 
 
594
 
 
595
 
class HintingSSHTransport(transport.Transport):
596
 
    """Simple transport that handles ssh:// and points out bzr+ssh://."""
597
 
 
598
 
    def __init__(self, url):
599
 
        raise errors.UnsupportedProtocol(url,
600
 
            'bzr supports bzr+ssh to operate over ssh, use "bzr+%s".' % url)
 
540
                                   http_transport=http_transport)
601
541
 
602
542
 
603
543
def get_test_permutations():
604
544
    """Return (transport, server) permutations for testing."""
605
545
    ### We may need a little more test framework support to construct an
606
546
    ### appropriate RemoteTransport in the future.
607
 
    from bzrlib.tests import test_server
608
 
    return [(RemoteTCPTransport, test_server.SmartTCPServer_for_testing)]
 
547
    from bzrlib.smart import server
 
548
    return [(RemoteTCPTransport, server.SmartTCPServer_for_testing)]