/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 brzlib/transport/remote.py

  • Committer: Jelmer Vernooij
  • Date: 2017-05-21 12:41:27 UTC
  • mto: This revision was merged to the branch mainline in revision 6623.
  • Revision ID: jelmer@jelmer.uk-20170521124127-iv8etg0vwymyai6y
s/bzr/brz/ in apport config.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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 brzlib.smart.
21
21
"""
22
22
 
 
23
from __future__ import absolute_import
 
24
 
23
25
__all__ = ['RemoteTransport', 'RemoteTCPTransport', 'RemoteSSHTransport']
24
26
 
25
 
from io import BytesIO
 
27
from cStringIO import StringIO
26
28
 
27
 
from .. import (
 
29
from brzlib import (
28
30
    config,
29
31
    debug,
30
32
    errors,
 
33
    remote,
31
34
    trace,
32
35
    transport,
33
36
    urlutils,
34
37
    )
35
 
from ..bzr import (
36
 
    remote,
37
 
    )
38
 
from ..bzr.smart import client, medium
 
38
from brzlib.smart import client, medium
39
39
 
40
40
 
41
41
class _SmartStat(object):
63
63
    """
64
64
 
65
65
    # When making a readv request, cap it at requesting 5MB of data
66
 
    _max_readv_bytes = 5 * 1024 * 1024
 
66
    _max_readv_bytes = 5*1024*1024
67
67
 
68
68
    # IMPORTANT FOR IMPLEMENTORS: RemoteTransport MUST NOT be given encoding
69
69
    # responsibilities: Put those on SmartClient or similar. This is vital for
99
99
        # what we want to share is really the shared connection.
100
100
 
101
101
        if (_from_transport is not None
102
 
                and isinstance(_from_transport, RemoteTransport)):
 
102
            and isinstance(_from_transport, RemoteTransport)):
103
103
            _client = _from_transport._client
104
104
        elif _from_transport is None:
105
105
            # If no _from_transport is specified, we need to intialize the
148
148
    def is_readonly(self):
149
149
        """Smart server transport can do read/write file operations."""
150
150
        try:
151
 
            resp = self._call2(b'Transport.is_readonly')
 
151
            resp = self._call2('Transport.is_readonly')
152
152
        except errors.UnknownSmartMethod:
153
153
            # XXX: nasty hack: servers before 0.16 don't have a
154
154
            # 'Transport.is_readonly' verb, so we do what clients before 0.16
155
155
            # did: assume False.
156
156
            return False
157
 
        if resp == (b'yes', ):
 
157
        if resp == ('yes', ):
158
158
            return True
159
 
        elif resp == (b'no', ):
 
159
        elif resp == ('no', ):
160
160
            return False
161
161
        else:
162
162
            raise errors.UnexpectedSmartServerResponse(resp)
169
169
 
170
170
    def _remote_path(self, relpath):
171
171
        """Returns the Unicode version of the absolute path for relpath."""
172
 
        path = urlutils.URL._combine_paths(self._parsed_url.path, relpath)
173
 
        if not isinstance(path, bytes):
174
 
            path = path.encode()
175
 
        return path
 
172
        return urlutils.URL._combine_paths(self._parsed_url.path, relpath)
176
173
 
177
174
    def _call(self, method, *args):
178
175
        resp = self._call2(method, *args)
182
179
        """Call a method on the remote server."""
183
180
        try:
184
181
            return self._client.call(method, *args)
185
 
        except errors.ErrorFromSmartServer as err:
 
182
        except errors.ErrorFromSmartServer, err:
186
183
            # The first argument, if present, is always a path.
187
184
            if args:
188
 
                context = {'relpath': args[0].decode('utf-8')}
 
185
                context = {'relpath': args[0]}
189
186
            else:
190
187
                context = {}
191
188
            self._translate_error(err, **context)
194
191
        """Call a method on the remote server with body bytes."""
195
192
        try:
196
193
            return self._client.call_with_body_bytes(method, args, body)
197
 
        except errors.ErrorFromSmartServer as err:
 
194
        except errors.ErrorFromSmartServer, err:
198
195
            # The first argument, if present, is always a path.
199
196
            if args:
200
197
                context = {'relpath': args[0]}
207
204
 
208
205
        :see: Transport.has()
209
206
        """
210
 
        resp = self._call2(b'has', self._remote_path(relpath))
211
 
        if resp == (b'yes', ):
 
207
        resp = self._call2('has', self._remote_path(relpath))
 
208
        if resp == ('yes', ):
212
209
            return True
213
 
        elif resp == (b'no', ):
 
210
        elif resp == ('no', ):
214
211
            return False
215
212
        else:
216
213
            raise errors.UnexpectedSmartServerResponse(resp)
220
217
 
221
218
        :see: Transport.get_bytes()/get_file()
222
219
        """
223
 
        return BytesIO(self.get_bytes(relpath))
 
220
        return StringIO(self.get_bytes(relpath))
224
221
 
225
222
    def get_bytes(self, relpath):
226
223
        remote = self._remote_path(relpath)
227
224
        try:
228
 
            resp, response_handler = self._client.call_expecting_body(
229
 
                b'get', remote)
230
 
        except errors.ErrorFromSmartServer as err:
 
225
            resp, response_handler = self._client.call_expecting_body('get', remote)
 
226
        except errors.ErrorFromSmartServer, err:
231
227
            self._translate_error(err, relpath)
232
 
        if resp != (b'ok', ):
 
228
        if resp != ('ok', ):
233
229
            response_handler.cancel_read_body()
234
230
            raise errors.UnexpectedSmartServerResponse(resp)
235
231
        return response_handler.read_body_bytes()
236
232
 
237
233
    def _serialise_optional_mode(self, mode):
238
234
        if mode is None:
239
 
            return b''
 
235
            return ''
240
236
        else:
241
 
            return ('%d' % mode).encode('ascii')
 
237
            return '%d' % mode
242
238
 
243
239
    def mkdir(self, relpath, mode=None):
244
 
        resp = self._call2(b'mkdir', self._remote_path(relpath),
245
 
                           self._serialise_optional_mode(mode))
 
240
        resp = self._call2('mkdir', self._remote_path(relpath),
 
241
            self._serialise_optional_mode(mode))
246
242
 
247
243
    def open_write_stream(self, relpath, mode=None):
248
244
        """See Transport.open_write_stream."""
249
 
        self.put_bytes(relpath, b"", mode)
 
245
        self.put_bytes(relpath, "", mode)
250
246
        result = transport.AppendBasedFileStream(self, relpath)
251
247
        transport._file_streams[self.abspath(relpath)] = result
252
248
        return result
253
249
 
254
250
    def put_bytes(self, relpath, raw_bytes, mode=None):
255
 
        if not isinstance(raw_bytes, bytes):
 
251
        if not isinstance(raw_bytes, str):
256
252
            raise TypeError(
257
 
                'raw_bytes must be bytes string, not %s' % type(raw_bytes))
 
253
                'raw_bytes must be a plain string, not %s' % type(raw_bytes))
258
254
        resp = self._call_with_body_bytes(
259
 
            b'put',
 
255
            'put',
260
256
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
261
257
            raw_bytes)
262
258
        self._ensure_ok(resp)
267
263
                             dir_mode=None):
268
264
        """See Transport.put_bytes_non_atomic."""
269
265
        # FIXME: no encoding in the transport!
270
 
        create_parent_str = b'F'
 
266
        create_parent_str = 'F'
271
267
        if create_parent_dir:
272
 
            create_parent_str = b'T'
 
268
            create_parent_str = 'T'
273
269
 
274
270
        resp = self._call_with_body_bytes(
275
 
            b'put_non_atomic',
 
271
            'put_non_atomic',
276
272
            (self._remote_path(relpath), self._serialise_optional_mode(mode),
277
273
             create_parent_str, self._serialise_optional_mode(dir_mode)),
278
274
            raw_bytes)
301
297
 
302
298
    def append_bytes(self, relpath, bytes, mode=None):
303
299
        resp = self._call_with_body_bytes(
304
 
            b'append',
 
300
            'append',
305
301
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
306
302
            bytes)
307
 
        if resp[0] == b'appended':
 
303
        if resp[0] == 'appended':
308
304
            return int(resp[1])
309
305
        raise errors.UnexpectedSmartServerResponse(resp)
310
306
 
311
307
    def delete(self, relpath):
312
 
        resp = self._call2(b'delete', self._remote_path(relpath))
 
308
        resp = self._call2('delete', self._remote_path(relpath))
313
309
        self._ensure_ok(resp)
314
310
 
315
311
    def external_url(self):
316
 
        """See breezy.transport.Transport.external_url."""
 
312
        """See brzlib.transport.Transport.external_url."""
317
313
        # the external path for RemoteTransports is the base
318
314
        return self.base
319
315
 
329
325
 
330
326
        sorted_offsets = sorted(offsets)
331
327
        coalesced = list(self._coalesce_offsets(sorted_offsets,
332
 
                                                limit=self._max_readv_combine,
333
 
                                                fudge_factor=self._bytes_to_read_before_seek,
334
 
                                                max_size=self._max_readv_bytes))
 
328
                               limit=self._max_readv_combine,
 
329
                               fudge_factor=self._bytes_to_read_before_seek,
 
330
                               max_size=self._max_readv_bytes))
335
331
 
336
332
        # now that we've coallesced things, avoid making enormous requests
337
333
        requests = []
357
353
        # turn the list of offsets into a single stack to iterate
358
354
        offset_stack = iter(offsets)
359
355
        # using a list so it can be modified when passing down and coming back
360
 
        next_offset = [next(offset_stack)]
 
356
        next_offset = [offset_stack.next()]
361
357
        for cur_request in requests:
362
358
            try:
363
359
                result = self._client.call_with_body_readv_array(
364
 
                    (b'readv', self._remote_path(relpath),),
 
360
                    ('readv', self._remote_path(relpath),),
365
361
                    [(c.start, c.length) for c in cur_request])
366
362
                resp, response_handler = result
367
 
            except errors.ErrorFromSmartServer as err:
 
363
            except errors.ErrorFromSmartServer, err:
368
364
                self._translate_error(err, relpath)
369
365
 
370
 
            if resp[0] != b'readv':
 
366
            if resp[0] != 'readv':
371
367
                # This should raise an exception
372
368
                response_handler.cancel_read_body()
373
369
                raise errors.UnexpectedSmartServerResponse(resp)
387
383
        for c_offset in coalesced:
388
384
            if len(data) < c_offset.length:
389
385
                raise errors.ShortReadvError(relpath, c_offset.start,
390
 
                                             c_offset.length, actual=len(data))
 
386
                            c_offset.length, actual=len(data))
391
387
            for suboffset, subsize in c_offset.ranges:
392
 
                key = (c_offset.start + suboffset, subsize)
393
 
                this_data = data[data_offset + suboffset:
394
 
                                 data_offset + suboffset + subsize]
 
388
                key = (c_offset.start+suboffset, subsize)
 
389
                this_data = data[data_offset+suboffset:
 
390
                                 data_offset+suboffset+subsize]
395
391
                # Special case when the data is in-order, rather than packing
396
392
                # into a map and then back out again. Benchmarking shows that
397
393
                # this has 100% hit rate, but leave in the data_map work just
401
397
                #       not have a real string.
402
398
                if key == cur_offset_and_size:
403
399
                    yield cur_offset_and_size[0], this_data
404
 
                    try:
405
 
                        cur_offset_and_size = next_offset[0] = next(
406
 
                            offset_stack)
407
 
                    except StopIteration:
408
 
                        return
 
400
                    cur_offset_and_size = next_offset[0] = offset_stack.next()
409
401
                else:
410
402
                    data_map[key] = this_data
411
403
            data_offset += c_offset.length
414
406
            while cur_offset_and_size in data_map:
415
407
                this_data = data_map.pop(cur_offset_and_size)
416
408
                yield cur_offset_and_size[0], this_data
417
 
                try:
418
 
                    cur_offset_and_size = next_offset[0] = next(offset_stack)
419
 
                except StopIteration:
420
 
                    return
 
409
                cur_offset_and_size = next_offset[0] = offset_stack.next()
421
410
 
422
411
    def rename(self, rel_from, rel_to):
423
 
        self._call(b'rename',
 
412
        self._call('rename',
424
413
                   self._remote_path(rel_from),
425
414
                   self._remote_path(rel_to))
426
415
 
427
416
    def move(self, rel_from, rel_to):
428
 
        self._call(b'move',
 
417
        self._call('move',
429
418
                   self._remote_path(rel_from),
430
419
                   self._remote_path(rel_to))
431
420
 
432
421
    def rmdir(self, relpath):
433
 
        resp = self._call(b'rmdir', self._remote_path(relpath))
 
422
        resp = self._call('rmdir', self._remote_path(relpath))
434
423
 
435
424
    def _ensure_ok(self, resp):
436
 
        if resp[0] != b'ok':
 
425
        if resp[0] != 'ok':
437
426
            raise errors.UnexpectedSmartServerResponse(resp)
438
427
 
439
428
    def _translate_error(self, err, relpath=None):
445
434
            m.disconnect()
446
435
 
447
436
    def stat(self, relpath):
448
 
        resp = self._call2(b'stat', self._remote_path(relpath))
449
 
        if resp[0] == b'stat':
 
437
        resp = self._call2('stat', self._remote_path(relpath))
 
438
        if resp[0] == 'stat':
450
439
            return _SmartStat(int(resp[1]), int(resp[2], 8))
451
440
        raise errors.UnexpectedSmartServerResponse(resp)
452
441
 
453
 
    # def lock_read(self, relpath):
454
 
    # """Lock the given file for shared (read) access.
455
 
    # :return: A lock object, which should be passed to Transport.unlock()
456
 
    # """
457
 
    # The old RemoteBranch ignore lock for reading, so we will
458
 
    # continue that tradition and return a bogus lock object.
459
 
    # class BogusLock(object):
460
 
    # def __init__(self, path):
 
442
    ## def lock_read(self, relpath):
 
443
    ##     """Lock the given file for shared (read) access.
 
444
    ##     :return: A lock object, which should be passed to Transport.unlock()
 
445
    ##     """
 
446
    ##     # The old RemoteBranch ignore lock for reading, so we will
 
447
    ##     # continue that tradition and return a bogus lock object.
 
448
    ##     class BogusLock(object):
 
449
    ##         def __init__(self, path):
461
450
    ##             self.path = path
462
 
    # def unlock(self):
463
 
    # pass
464
 
    # return BogusLock(relpath)
 
451
    ##         def unlock(self):
 
452
    ##             pass
 
453
    ##     return BogusLock(relpath)
465
454
 
466
455
    def listable(self):
467
456
        return True
468
457
 
469
458
    def list_dir(self, relpath):
470
 
        resp = self._call2(b'list_dir', self._remote_path(relpath))
471
 
        if resp[0] == b'names':
472
 
            return [name.decode('utf-8') for name in resp[1:]]
 
459
        resp = self._call2('list_dir', self._remote_path(relpath))
 
460
        if resp[0] == 'names':
 
461
            return [name.encode('ascii') for name in resp[1:]]
473
462
        raise errors.UnexpectedSmartServerResponse(resp)
474
463
 
475
464
    def iter_files_recursive(self):
476
 
        resp = self._call2(b'iter_files_recursive', self._remote_path(''))
477
 
        if resp[0] == b'names':
478
 
            return [name.decode('utf-8') for name in resp[1:]]
 
465
        resp = self._call2('iter_files_recursive', self._remote_path(''))
 
466
        if resp[0] == 'names':
 
467
            return resp[1:]
479
468
        raise errors.UnexpectedSmartServerResponse(resp)
480
469
 
481
470
 
521
510
        if user is None:
522
511
            auth = config.AuthenticationConfig()
523
512
            user = auth.get_user('ssh', self._parsed_url.host,
524
 
                                 self._parsed_url.port)
 
513
                self._parsed_url.port)
525
514
        ssh_params = medium.SSHParams(self._parsed_url.host,
526
 
                                      self._parsed_url.port, user, self._parsed_url.password,
527
 
                                      bzr_remote_path)
 
515
                self._parsed_url.port, user, self._parsed_url.password,
 
516
                bzr_remote_path)
528
517
        client_medium = medium.SmartSSHClientMedium(self.base, ssh_params)
529
518
        return client_medium, (user, self._parsed_url.password)
530
519
 
590
579
        """See transport._redirected_to"""
591
580
        redirected = self._http_transport._redirected_to(source, target)
592
581
        if (redirected is not None
593
 
                and isinstance(redirected, type(self._http_transport))):
 
582
            and isinstance(redirected, type(self._http_transport))):
594
583
            return RemoteHTTPTransport('bzr+' + redirected.external_url(),
595
584
                                       http_transport=redirected)
596
585
        else:
599
588
 
600
589
 
601
590
class HintingSSHTransport(transport.Transport):
602
 
    """Simple transport that handles ssh:// and points out bzr+ssh:// and git+ssh://."""
603
 
 
604
 
    # TODO(jelmer): Implement support for detecting whether the repository at the
605
 
    # other end is a git or bzr repository.
 
591
    """Simple transport that handles ssh:// and points out bzr+ssh://."""
606
592
 
607
593
    def __init__(self, url):
608
 
        raise errors.UnsupportedProtocol(
609
 
            url, 'Use bzr+ssh for Bazaar operations over SSH, e.g. "bzr+%s". '
610
 
            'Use git+ssh for Git operations over SSH, e.g. "git+%s".' % (url, url))
 
594
        raise errors.UnsupportedProtocol(url,
 
595
            'bzr supports bzr+ssh to operate over ssh, use "bzr+%s".' % url)
611
596
 
612
597
 
613
598
def get_test_permutations():
614
599
    """Return (transport, server) permutations for testing."""
615
 
    # We may need a little more test framework support to construct an
616
 
    # appropriate RemoteTransport in the future.
617
 
    from ..tests import test_server
 
600
    ### We may need a little more test framework support to construct an
 
601
    ### appropriate RemoteTransport in the future.
 
602
    from brzlib.tests import test_server
618
603
    return [(RemoteTCPTransport, test_server.SmartTCPServer_for_testing)]