/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: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2012, 2016 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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
17
17
"""RemoteTransport client for the smart-server.
18
18
 
19
19
This module shouldn't be accessed directly.  The classes defined here should be
20
 
imported from breezy.bzr.smart.
 
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
 
from io import BytesIO
 
25
from cStringIO import StringIO
28
26
 
29
 
from .. import (
 
27
from bzrlib import (
30
28
    config,
31
29
    debug,
32
30
    errors,
 
31
    remote,
33
32
    trace,
34
33
    transport,
35
34
    urlutils,
36
35
    )
37
 
from ..bzr import (
38
 
    remote,
 
36
from bzrlib.smart import client, medium
 
37
from bzrlib.symbol_versioning import (
 
38
    deprecated_method,
39
39
    )
40
 
from ..sixish import PY3
41
 
from ..bzr.smart import client, medium
42
40
 
43
41
 
44
42
class _SmartStat(object):
66
64
    """
67
65
 
68
66
    # When making a readv request, cap it at requesting 5MB of data
69
 
    _max_readv_bytes = 5 * 1024 * 1024
 
67
    _max_readv_bytes = 5*1024*1024
70
68
 
71
69
    # IMPORTANT FOR IMPLEMENTORS: RemoteTransport MUST NOT be given encoding
72
70
    # responsibilities: Put those on SmartClient or similar. This is vital for
102
100
        # what we want to share is really the shared connection.
103
101
 
104
102
        if (_from_transport is not None
105
 
                and isinstance(_from_transport, RemoteTransport)):
 
103
            and isinstance(_from_transport, RemoteTransport)):
106
104
            _client = _from_transport._client
107
105
        elif _from_transport is None:
108
106
            # If no _from_transport is specified, we need to intialize the
151
149
    def is_readonly(self):
152
150
        """Smart server transport can do read/write file operations."""
153
151
        try:
154
 
            resp = self._call2(b'Transport.is_readonly')
 
152
            resp = self._call2('Transport.is_readonly')
155
153
        except errors.UnknownSmartMethod:
156
154
            # XXX: nasty hack: servers before 0.16 don't have a
157
155
            # 'Transport.is_readonly' verb, so we do what clients before 0.16
158
156
            # did: assume False.
159
157
            return False
160
 
        if resp == (b'yes', ):
 
158
        if resp == ('yes', ):
161
159
            return True
162
 
        elif resp == (b'no', ):
 
160
        elif resp == ('no', ):
163
161
            return False
164
162
        else:
165
163
            raise errors.UnexpectedSmartServerResponse(resp)
172
170
 
173
171
    def _remote_path(self, relpath):
174
172
        """Returns the Unicode version of the absolute path for relpath."""
175
 
        path = urlutils.URL._combine_paths(self._parsed_url.path, relpath)
176
 
        if not isinstance(path, bytes):
177
 
            path = path.encode()
178
 
        return path
 
173
        return self._combine_paths(self._path, relpath)
179
174
 
180
175
    def _call(self, method, *args):
181
176
        resp = self._call2(method, *args)
185
180
        """Call a method on the remote server."""
186
181
        try:
187
182
            return self._client.call(method, *args)
188
 
        except errors.ErrorFromSmartServer as err:
 
183
        except errors.ErrorFromSmartServer, err:
189
184
            # The first argument, if present, is always a path.
190
185
            if args:
191
 
                context = {'relpath': args[0].decode('utf-8')}
 
186
                context = {'relpath': args[0]}
192
187
            else:
193
188
                context = {}
194
189
            self._translate_error(err, **context)
197
192
        """Call a method on the remote server with body bytes."""
198
193
        try:
199
194
            return self._client.call_with_body_bytes(method, args, body)
200
 
        except errors.ErrorFromSmartServer as err:
 
195
        except errors.ErrorFromSmartServer, err:
201
196
            # The first argument, if present, is always a path.
202
197
            if args:
203
198
                context = {'relpath': args[0]}
210
205
 
211
206
        :see: Transport.has()
212
207
        """
213
 
        resp = self._call2(b'has', self._remote_path(relpath))
214
 
        if resp == (b'yes', ):
 
208
        resp = self._call2('has', self._remote_path(relpath))
 
209
        if resp == ('yes', ):
215
210
            return True
216
 
        elif resp == (b'no', ):
 
211
        elif resp == ('no', ):
217
212
            return False
218
213
        else:
219
214
            raise errors.UnexpectedSmartServerResponse(resp)
223
218
 
224
219
        :see: Transport.get_bytes()/get_file()
225
220
        """
226
 
        return BytesIO(self.get_bytes(relpath))
 
221
        return StringIO(self.get_bytes(relpath))
227
222
 
228
223
    def get_bytes(self, relpath):
229
224
        remote = self._remote_path(relpath)
230
225
        try:
231
 
            resp, response_handler = self._client.call_expecting_body(
232
 
                b'get', remote)
233
 
        except errors.ErrorFromSmartServer as err:
 
226
            resp, response_handler = self._client.call_expecting_body('get', remote)
 
227
        except errors.ErrorFromSmartServer, err:
234
228
            self._translate_error(err, relpath)
235
 
        if resp != (b'ok', ):
 
229
        if resp != ('ok', ):
236
230
            response_handler.cancel_read_body()
237
231
            raise errors.UnexpectedSmartServerResponse(resp)
238
232
        return response_handler.read_body_bytes()
239
233
 
240
234
    def _serialise_optional_mode(self, mode):
241
235
        if mode is None:
242
 
            return b''
 
236
            return ''
243
237
        else:
244
 
            return ('%d' % mode).encode('ascii')
 
238
            return '%d' % mode
245
239
 
246
240
    def mkdir(self, relpath, mode=None):
247
 
        resp = self._call2(b'mkdir', self._remote_path(relpath),
248
 
                           self._serialise_optional_mode(mode))
 
241
        resp = self._call2('mkdir', self._remote_path(relpath),
 
242
            self._serialise_optional_mode(mode))
249
243
 
250
244
    def open_write_stream(self, relpath, mode=None):
251
245
        """See Transport.open_write_stream."""
252
 
        self.put_bytes(relpath, b"", mode)
 
246
        self.put_bytes(relpath, "", mode)
253
247
        result = transport.AppendBasedFileStream(self, relpath)
254
248
        transport._file_streams[self.abspath(relpath)] = result
255
249
        return result
256
250
 
257
 
    def put_bytes(self, relpath, raw_bytes, mode=None):
258
 
        if not isinstance(raw_bytes, bytes):
259
 
            raise TypeError(
260
 
                'raw_bytes must be bytes string, not %s' % type(raw_bytes))
261
 
        resp = self._call_with_body_bytes(
262
 
            b'put',
 
251
    def put_bytes(self, relpath, upload_contents, mode=None):
 
252
        # FIXME: upload_file is probably not safe for non-ascii characters -
 
253
        # should probably just pass all parameters as length-delimited
 
254
        # strings?
 
255
        if type(upload_contents) is unicode:
 
256
            # Although not strictly correct, we raise UnicodeEncodeError to be
 
257
            # compatible with other transports.
 
258
            raise UnicodeEncodeError(
 
259
                'undefined', upload_contents, 0, 1,
 
260
                'put_bytes must be given bytes, not unicode.')
 
261
        resp = self._call_with_body_bytes('put',
263
262
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
264
 
            raw_bytes)
 
263
            upload_contents)
265
264
        self._ensure_ok(resp)
266
 
        return len(raw_bytes)
 
265
        return len(upload_contents)
267
266
 
268
 
    def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
 
267
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
269
268
                             create_parent_dir=False,
270
269
                             dir_mode=None):
271
270
        """See Transport.put_bytes_non_atomic."""
272
271
        # FIXME: no encoding in the transport!
273
 
        create_parent_str = b'F'
 
272
        create_parent_str = 'F'
274
273
        if create_parent_dir:
275
 
            create_parent_str = b'T'
 
274
            create_parent_str = 'T'
276
275
 
277
276
        resp = self._call_with_body_bytes(
278
 
            b'put_non_atomic',
 
277
            'put_non_atomic',
279
278
            (self._remote_path(relpath), self._serialise_optional_mode(mode),
280
279
             create_parent_str, self._serialise_optional_mode(dir_mode)),
281
 
            raw_bytes)
 
280
            bytes)
282
281
        self._ensure_ok(resp)
283
282
 
284
283
    def put_file(self, relpath, upload_file, mode=None):
304
303
 
305
304
    def append_bytes(self, relpath, bytes, mode=None):
306
305
        resp = self._call_with_body_bytes(
307
 
            b'append',
 
306
            'append',
308
307
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
309
308
            bytes)
310
 
        if resp[0] == b'appended':
 
309
        if resp[0] == 'appended':
311
310
            return int(resp[1])
312
311
        raise errors.UnexpectedSmartServerResponse(resp)
313
312
 
314
313
    def delete(self, relpath):
315
 
        resp = self._call2(b'delete', self._remote_path(relpath))
 
314
        resp = self._call2('delete', self._remote_path(relpath))
316
315
        self._ensure_ok(resp)
317
316
 
318
317
    def external_url(self):
319
 
        """See breezy.transport.Transport.external_url."""
 
318
        """See bzrlib.transport.Transport.external_url."""
320
319
        # the external path for RemoteTransports is the base
321
320
        return self.base
322
321
 
332
331
 
333
332
        sorted_offsets = sorted(offsets)
334
333
        coalesced = list(self._coalesce_offsets(sorted_offsets,
335
 
                                                limit=self._max_readv_combine,
336
 
                                                fudge_factor=self._bytes_to_read_before_seek,
337
 
                                                max_size=self._max_readv_bytes))
 
334
                               limit=self._max_readv_combine,
 
335
                               fudge_factor=self._bytes_to_read_before_seek,
 
336
                               max_size=self._max_readv_bytes))
338
337
 
339
338
        # now that we've coallesced things, avoid making enormous requests
340
339
        requests = []
360
359
        # turn the list of offsets into a single stack to iterate
361
360
        offset_stack = iter(offsets)
362
361
        # using a list so it can be modified when passing down and coming back
363
 
        next_offset = [next(offset_stack)]
 
362
        next_offset = [offset_stack.next()]
364
363
        for cur_request in requests:
365
364
            try:
366
365
                result = self._client.call_with_body_readv_array(
367
 
                    (b'readv', self._remote_path(relpath),),
 
366
                    ('readv', self._remote_path(relpath),),
368
367
                    [(c.start, c.length) for c in cur_request])
369
368
                resp, response_handler = result
370
 
            except errors.ErrorFromSmartServer as err:
 
369
            except errors.ErrorFromSmartServer, err:
371
370
                self._translate_error(err, relpath)
372
371
 
373
 
            if resp[0] != b'readv':
 
372
            if resp[0] != 'readv':
374
373
                # This should raise an exception
375
374
                response_handler.cancel_read_body()
376
375
                raise errors.UnexpectedSmartServerResponse(resp)
390
389
        for c_offset in coalesced:
391
390
            if len(data) < c_offset.length:
392
391
                raise errors.ShortReadvError(relpath, c_offset.start,
393
 
                                             c_offset.length, actual=len(data))
 
392
                            c_offset.length, actual=len(data))
394
393
            for suboffset, subsize in c_offset.ranges:
395
 
                key = (c_offset.start + suboffset, subsize)
396
 
                this_data = data[data_offset + suboffset:
397
 
                                 data_offset + suboffset + subsize]
 
394
                key = (c_offset.start+suboffset, subsize)
 
395
                this_data = data[data_offset+suboffset:
 
396
                                 data_offset+suboffset+subsize]
398
397
                # Special case when the data is in-order, rather than packing
399
398
                # into a map and then back out again. Benchmarking shows that
400
399
                # this has 100% hit rate, but leave in the data_map work just
404
403
                #       not have a real string.
405
404
                if key == cur_offset_and_size:
406
405
                    yield cur_offset_and_size[0], this_data
407
 
                    try:
408
 
                        cur_offset_and_size = next_offset[0] = next(
409
 
                            offset_stack)
410
 
                    except StopIteration:
411
 
                        return
 
406
                    cur_offset_and_size = next_offset[0] = offset_stack.next()
412
407
                else:
413
408
                    data_map[key] = this_data
414
409
            data_offset += c_offset.length
417
412
            while cur_offset_and_size in data_map:
418
413
                this_data = data_map.pop(cur_offset_and_size)
419
414
                yield cur_offset_and_size[0], this_data
420
 
                try:
421
 
                    cur_offset_and_size = next_offset[0] = next(offset_stack)
422
 
                except StopIteration:
423
 
                    return
 
415
                cur_offset_and_size = next_offset[0] = offset_stack.next()
424
416
 
425
417
    def rename(self, rel_from, rel_to):
426
 
        self._call(b'rename',
 
418
        self._call('rename',
427
419
                   self._remote_path(rel_from),
428
420
                   self._remote_path(rel_to))
429
421
 
430
422
    def move(self, rel_from, rel_to):
431
 
        self._call(b'move',
 
423
        self._call('move',
432
424
                   self._remote_path(rel_from),
433
425
                   self._remote_path(rel_to))
434
426
 
435
427
    def rmdir(self, relpath):
436
 
        resp = self._call(b'rmdir', self._remote_path(relpath))
 
428
        resp = self._call('rmdir', self._remote_path(relpath))
437
429
 
438
430
    def _ensure_ok(self, resp):
439
 
        if resp[0] != b'ok':
 
431
        if resp[0] != 'ok':
440
432
            raise errors.UnexpectedSmartServerResponse(resp)
441
433
 
442
434
    def _translate_error(self, err, relpath=None):
443
435
        remote._translate_error(err, path=relpath)
444
436
 
445
437
    def disconnect(self):
446
 
        m = self.get_smart_medium()
447
 
        if m is not None:
448
 
            m.disconnect()
 
438
        self.get_smart_medium().disconnect()
449
439
 
450
440
    def stat(self, relpath):
451
 
        resp = self._call2(b'stat', self._remote_path(relpath))
452
 
        if resp[0] == b'stat':
 
441
        resp = self._call2('stat', self._remote_path(relpath))
 
442
        if resp[0] == 'stat':
453
443
            return _SmartStat(int(resp[1]), int(resp[2], 8))
454
444
        raise errors.UnexpectedSmartServerResponse(resp)
455
445
 
456
 
    # def lock_read(self, relpath):
457
 
    # """Lock the given file for shared (read) access.
458
 
    # :return: A lock object, which should be passed to Transport.unlock()
459
 
    # """
460
 
    # The old RemoteBranch ignore lock for reading, so we will
461
 
    # continue that tradition and return a bogus lock object.
462
 
    # class BogusLock(object):
463
 
    # def __init__(self, path):
 
446
    ## def lock_read(self, relpath):
 
447
    ##     """Lock the given file for shared (read) access.
 
448
    ##     :return: A lock object, which should be passed to Transport.unlock()
 
449
    ##     """
 
450
    ##     # The old RemoteBranch ignore lock for reading, so we will
 
451
    ##     # continue that tradition and return a bogus lock object.
 
452
    ##     class BogusLock(object):
 
453
    ##         def __init__(self, path):
464
454
    ##             self.path = path
465
 
    # def unlock(self):
466
 
    # pass
467
 
    # return BogusLock(relpath)
 
455
    ##         def unlock(self):
 
456
    ##             pass
 
457
    ##     return BogusLock(relpath)
468
458
 
469
459
    def listable(self):
470
460
        return True
471
461
 
472
462
    def list_dir(self, relpath):
473
 
        resp = self._call2(b'list_dir', self._remote_path(relpath))
474
 
        if resp[0] == b'names':
475
 
            return [name.decode('utf-8') if PY3 else name for name in resp[1:]]
 
463
        resp = self._call2('list_dir', self._remote_path(relpath))
 
464
        if resp[0] == 'names':
 
465
            return [name.encode('ascii') for name in resp[1:]]
476
466
        raise errors.UnexpectedSmartServerResponse(resp)
477
467
 
478
468
    def iter_files_recursive(self):
479
 
        resp = self._call2(b'iter_files_recursive', self._remote_path(''))
480
 
        if resp[0] == b'names':
481
 
            return [name.decode('utf-8') if PY3 else name for name in resp[1:]]
 
469
        resp = self._call2('iter_files_recursive', self._remote_path(''))
 
470
        if resp[0] == 'names':
 
471
            return resp[1:]
482
472
        raise errors.UnexpectedSmartServerResponse(resp)
483
473
 
484
474
 
491
481
 
492
482
    def _build_medium(self):
493
483
        client_medium = medium.SmartTCPClientMedium(
494
 
            self._parsed_url.host, self._parsed_url.port, self.base)
 
484
            self._host, self._port, self.base)
495
485
        return client_medium, None
496
486
 
497
487
 
504
494
 
505
495
    def _build_medium(self):
506
496
        client_medium = medium.SmartTCPClientMedium(
507
 
            self._parsed_url.host, self._parsed_url.port, self.base)
 
497
            self._host, self._port, self.base)
508
498
        client_medium._protocol_version = 2
509
499
        client_medium._remember_remote_is_before((1, 6))
510
500
        return client_medium, None
520
510
    def _build_medium(self):
521
511
        location_config = config.LocationConfig(self.base)
522
512
        bzr_remote_path = location_config.get_bzr_remote_path()
523
 
        user = self._parsed_url.user
 
513
        user = self._user
524
514
        if user is None:
525
515
            auth = config.AuthenticationConfig()
526
 
            user = auth.get_user('ssh', self._parsed_url.host,
527
 
                                 self._parsed_url.port)
528
 
        ssh_params = medium.SSHParams(self._parsed_url.host,
529
 
                                      self._parsed_url.port, user, self._parsed_url.password,
530
 
                                      bzr_remote_path)
 
516
            user = auth.get_user('ssh', self._host, self._port)
 
517
        ssh_params = medium.SSHParams(self._host, self._port, user,
 
518
            self._password, bzr_remote_path)
531
519
        client_medium = medium.SmartSSHClientMedium(self.base, ssh_params)
532
 
        return client_medium, (user, self._parsed_url.password)
 
520
        return client_medium, (user, self._password)
533
521
 
534
522
 
535
523
class RemoteHTTPTransport(RemoteTransport):
549
537
            # url only for an intial construction (when the url came from the
550
538
            # command-line).
551
539
            http_url = base[len('bzr+'):]
552
 
            self._http_transport = transport.get_transport_from_url(http_url)
 
540
            self._http_transport = transport.get_transport(http_url)
553
541
        else:
554
542
            self._http_transport = http_transport
555
543
        super(RemoteHTTPTransport, self).__init__(
593
581
        """See transport._redirected_to"""
594
582
        redirected = self._http_transport._redirected_to(source, target)
595
583
        if (redirected is not None
596
 
                and isinstance(redirected, type(self._http_transport))):
 
584
            and isinstance(redirected, type(self._http_transport))):
597
585
            return RemoteHTTPTransport('bzr+' + redirected.external_url(),
598
586
                                       http_transport=redirected)
599
587
        else:
602
590
 
603
591
 
604
592
class HintingSSHTransport(transport.Transport):
605
 
    """Simple transport that handles ssh:// and points out bzr+ssh:// and git+ssh://."""
606
 
 
607
 
    # TODO(jelmer): Implement support for detecting whether the repository at the
608
 
    # other end is a git or bzr repository.
 
593
    """Simple transport that handles ssh:// and points out bzr+ssh://."""
609
594
 
610
595
    def __init__(self, url):
611
 
        raise errors.UnsupportedProtocol(
612
 
            url, 'Use bzr+ssh for Bazaar operations over SSH, e.g. "bzr+%s". '
613
 
            'Use git+ssh for Git operations over SSH, e.g. "git+%s".' % (url, url))
 
596
        raise errors.UnsupportedProtocol(url,
 
597
            'bzr supports bzr+ssh to operate over ssh, use "bzr+%s".' % url)
614
598
 
615
599
 
616
600
def get_test_permutations():
617
601
    """Return (transport, server) permutations for testing."""
618
 
    # We may need a little more test framework support to construct an
619
 
    # appropriate RemoteTransport in the future.
620
 
    from ..tests import test_server
 
602
    ### We may need a little more test framework support to construct an
 
603
    ### appropriate RemoteTransport in the future.
 
604
    from bzrlib.tests import test_server
621
605
    return [(RemoteTCPTransport, test_server.SmartTCPServer_for_testing)]