/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: John Arbash Meinel
  • Date: 2008-07-09 21:42:24 UTC
  • mto: This revision was merged to the branch mainline in revision 3543.
  • Revision ID: john@arbash-meinel.com-20080709214224-r75k87r6a01pfc3h
Restore a real weave merge to 'bzr merge --weave'.

To do so efficiently, we only add the simple LCAs to the final weave
object, unless we run into complexities with the merge graph.
This gives the same effective result as adding all the texts,
with the advantage of not having to extract all of them.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
28
28
    config,
29
29
    debug,
30
30
    errors,
31
 
    remote,
32
31
    trace,
33
32
    transport,
34
33
    urlutils,
35
34
    )
36
35
from bzrlib.smart import client, medium
37
 
from bzrlib.symbol_versioning import (
38
 
    deprecated_method,
39
 
    )
 
36
from bzrlib.symbol_versioning import (deprecated_method, one_four)
40
37
 
41
38
 
42
39
class _SmartStat(object):
54
51
 
55
52
    The connection has a notion of the current directory to which it's
56
53
    connected; this is incorporated in filenames passed to the server.
57
 
 
58
 
    This supports some higher-level RPC operations and can also be treated
 
54
    
 
55
    This supports some higher-level RPC operations and can also be treated 
59
56
    like a Transport to do file-like operations.
60
57
 
61
58
    The connection can be made over a tcp socket, an ssh pipe or a series of
63
60
    RemoteTCPTransport, etc.
64
61
    """
65
62
 
66
 
    # When making a readv request, cap it at requesting 5MB of data
67
 
    _max_readv_bytes = 5*1024*1024
68
 
 
69
63
    # IMPORTANT FOR IMPLEMENTORS: RemoteTransport MUST NOT be given encoding
70
64
    # responsibilities: Put those on SmartClient or similar. This is vital for
71
65
    # the ability to support multiple versions of the smart protocol over time:
72
 
    # RemoteTransport is an adapter from the Transport object model to the
 
66
    # RemoteTransport is an adapter from the Transport object model to the 
73
67
    # SmartClient model, not an encoder.
74
68
 
75
69
    # FIXME: the medium parameter should be private, only the tests requires
92
86
            should only be used for testing purposes; normally this is
93
87
            determined from the medium.
94
88
        """
95
 
        super(RemoteTransport, self).__init__(
96
 
            url, _from_transport=_from_transport)
 
89
        super(RemoteTransport, self).__init__(url,
 
90
                                              _from_transport=_from_transport)
97
91
 
98
92
        # The medium is the connection, except when we need to share it with
99
93
        # other objects (RemoteBzrDir, RemoteRepository etc). In these cases
100
94
        # what we want to share is really the shared connection.
101
95
 
102
 
        if (_from_transport is not None
103
 
            and isinstance(_from_transport, RemoteTransport)):
104
 
            _client = _from_transport._client
105
 
        elif _from_transport is None:
 
96
        if _from_transport is None:
106
97
            # If no _from_transport is specified, we need to intialize the
107
98
            # shared medium.
108
99
            credentials = None
138
129
        # No credentials
139
130
        return None, None
140
131
 
141
 
    def _report_activity(self, bytes, direction):
142
 
        """See Transport._report_activity.
143
 
 
144
 
        Does nothing; the smart medium will report activity triggered by a
145
 
        RemoteTransport.
146
 
        """
147
 
        pass
148
 
 
149
132
    def is_readonly(self):
150
133
        """Smart server transport can do read/write file operations."""
151
134
        try:
160
143
        elif resp == ('no', ):
161
144
            return False
162
145
        else:
163
 
            raise errors.UnexpectedSmartServerResponse(resp)
 
146
            self._translate_error(resp)
 
147
        raise errors.UnexpectedSmartServerResponse(resp)
164
148
 
165
149
    def get_smart_client(self):
166
150
        return self._get_connection()
168
152
    def get_smart_medium(self):
169
153
        return self._get_connection()
170
154
 
 
155
    @deprecated_method(one_four)
 
156
    def get_shared_medium(self):
 
157
        return self._get_shared_connection()
 
158
 
171
159
    def _remote_path(self, relpath):
172
160
        """Returns the Unicode version of the absolute path for relpath."""
173
161
        return self._combine_paths(self._path, relpath)
174
162
 
175
163
    def _call(self, method, *args):
176
 
        resp = self._call2(method, *args)
177
 
        self._ensure_ok(resp)
 
164
        try:
 
165
            resp = self._call2(method, *args)
 
166
        except errors.ErrorFromSmartServer, err:
 
167
            self._translate_error(err.error_tuple)
 
168
        self._translate_error(resp)
178
169
 
179
170
    def _call2(self, method, *args):
180
171
        """Call a method on the remote server."""
181
172
        try:
182
173
            return self._client.call(method, *args)
183
174
        except errors.ErrorFromSmartServer, err:
184
 
            # The first argument, if present, is always a path.
185
 
            if args:
186
 
                context = {'relpath': args[0]}
187
 
            else:
188
 
                context = {}
189
 
            self._translate_error(err, **context)
 
175
            self._translate_error(err.error_tuple)
190
176
 
191
177
    def _call_with_body_bytes(self, method, args, body):
192
178
        """Call a method on the remote server with body bytes."""
193
179
        try:
194
180
            return self._client.call_with_body_bytes(method, args, body)
195
181
        except errors.ErrorFromSmartServer, err:
196
 
            # The first argument, if present, is always a path.
197
 
            if args:
198
 
                context = {'relpath': args[0]}
199
 
            else:
200
 
                context = {}
201
 
            self._translate_error(err, **context)
 
182
            self._translate_error(err.error_tuple)
202
183
 
203
184
    def has(self, relpath):
204
185
        """Indicate whether a remote file of the given name exists or not.
211
192
        elif resp == ('no', ):
212
193
            return False
213
194
        else:
214
 
            raise errors.UnexpectedSmartServerResponse(resp)
 
195
            self._translate_error(resp)
215
196
 
216
197
    def get(self, relpath):
217
198
        """Return file-like object reading the contents of a remote file.
218
 
 
 
199
        
219
200
        :see: Transport.get_bytes()/get_file()
220
201
        """
221
202
        return StringIO(self.get_bytes(relpath))
225
206
        try:
226
207
            resp, response_handler = self._client.call_expecting_body('get', remote)
227
208
        except errors.ErrorFromSmartServer, err:
228
 
            self._translate_error(err, relpath)
 
209
            self._translate_error(err.error_tuple, relpath)
229
210
        if resp != ('ok', ):
230
211
            response_handler.cancel_read_body()
231
212
            raise errors.UnexpectedSmartServerResponse(resp)
240
221
    def mkdir(self, relpath, mode=None):
241
222
        resp = self._call2('mkdir', self._remote_path(relpath),
242
223
            self._serialise_optional_mode(mode))
 
224
        self._translate_error(resp)
243
225
 
244
226
    def open_write_stream(self, relpath, mode=None):
245
227
        """See Transport.open_write_stream."""
261
243
        resp = self._call_with_body_bytes('put',
262
244
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
263
245
            upload_contents)
264
 
        self._ensure_ok(resp)
 
246
        self._translate_error(resp)
265
247
        return len(upload_contents)
266
248
 
267
249
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
278
260
            (self._remote_path(relpath), self._serialise_optional_mode(mode),
279
261
             create_parent_str, self._serialise_optional_mode(dir_mode)),
280
262
            bytes)
281
 
        self._ensure_ok(resp)
 
263
        self._translate_error(resp)
282
264
 
283
265
    def put_file(self, relpath, upload_file, mode=None):
284
266
        # its not ideal to seek back, but currently put_non_atomic_file depends
300
282
 
301
283
    def append_file(self, relpath, from_file, mode=None):
302
284
        return self.append_bytes(relpath, from_file.read(), mode)
303
 
 
 
285
        
304
286
    def append_bytes(self, relpath, bytes, mode=None):
305
287
        resp = self._call_with_body_bytes(
306
288
            'append',
308
290
            bytes)
309
291
        if resp[0] == 'appended':
310
292
            return int(resp[1])
311
 
        raise errors.UnexpectedSmartServerResponse(resp)
 
293
        self._translate_error(resp)
312
294
 
313
295
    def delete(self, relpath):
314
296
        resp = self._call2('delete', self._remote_path(relpath))
315
 
        self._ensure_ok(resp)
 
297
        self._translate_error(resp)
316
298
 
317
299
    def external_url(self):
318
300
        """See bzrlib.transport.Transport.external_url."""
322
304
    def recommended_page_size(self):
323
305
        """Return the recommended page size for this transport."""
324
306
        return 64 * 1024
325
 
 
 
307
        
326
308
    def _readv(self, relpath, offsets):
327
309
        if not offsets:
328
310
            return
330
312
        offsets = list(offsets)
331
313
 
332
314
        sorted_offsets = sorted(offsets)
 
315
        # turn the list of offsets into a stack
 
316
        offset_stack = iter(offsets)
 
317
        cur_offset_and_size = offset_stack.next()
333
318
        coalesced = list(self._coalesce_offsets(sorted_offsets,
334
319
                               limit=self._max_readv_combine,
335
 
                               fudge_factor=self._bytes_to_read_before_seek,
336
 
                               max_size=self._max_readv_bytes))
337
 
 
338
 
        # now that we've coallesced things, avoid making enormous requests
339
 
        requests = []
340
 
        cur_request = []
341
 
        cur_len = 0
342
 
        for c in coalesced:
343
 
            if c.length + cur_len > self._max_readv_bytes:
344
 
                requests.append(cur_request)
345
 
                cur_request = [c]
346
 
                cur_len = c.length
347
 
                continue
348
 
            cur_request.append(c)
349
 
            cur_len += c.length
350
 
        if cur_request:
351
 
            requests.append(cur_request)
352
 
        if 'hpss' in debug.debug_flags:
353
 
            trace.mutter('%s.readv %s offsets => %s coalesced'
354
 
                         ' => %s requests (%s)',
355
 
                         self.__class__.__name__, len(offsets), len(coalesced),
356
 
                         len(requests), sum(map(len, requests)))
 
320
                               fudge_factor=self._bytes_to_read_before_seek))
 
321
 
 
322
        try:
 
323
            result = self._client.call_with_body_readv_array(
 
324
                ('readv', self._remote_path(relpath),),
 
325
                [(c.start, c.length) for c in coalesced])
 
326
            resp, response_handler = result
 
327
        except errors.ErrorFromSmartServer, err:
 
328
            self._translate_error(err.error_tuple)
 
329
 
 
330
        if resp[0] != 'readv':
 
331
            # This should raise an exception
 
332
            response_handler.cancel_read_body()
 
333
            raise errors.UnexpectedSmartServerResponse(resp)
 
334
 
 
335
        # FIXME: this should know how many bytes are needed, for clarity.
 
336
        data = response_handler.read_body_bytes()
357
337
        # Cache the results, but only until they have been fulfilled
358
338
        data_map = {}
359
 
        # turn the list of offsets into a single stack to iterate
360
 
        offset_stack = iter(offsets)
361
 
        # using a list so it can be modified when passing down and coming back
362
 
        next_offset = [offset_stack.next()]
363
 
        for cur_request in requests:
364
 
            try:
365
 
                result = self._client.call_with_body_readv_array(
366
 
                    ('readv', self._remote_path(relpath),),
367
 
                    [(c.start, c.length) for c in cur_request])
368
 
                resp, response_handler = result
369
 
            except errors.ErrorFromSmartServer, err:
370
 
                self._translate_error(err, relpath)
371
 
 
372
 
            if resp[0] != 'readv':
373
 
                # This should raise an exception
374
 
                response_handler.cancel_read_body()
375
 
                raise errors.UnexpectedSmartServerResponse(resp)
376
 
 
377
 
            for res in self._handle_response(offset_stack, cur_request,
378
 
                                             response_handler,
379
 
                                             data_map,
380
 
                                             next_offset):
381
 
                yield res
382
 
 
383
 
    def _handle_response(self, offset_stack, coalesced, response_handler,
384
 
                         data_map, next_offset):
385
 
        cur_offset_and_size = next_offset[0]
386
 
        # FIXME: this should know how many bytes are needed, for clarity.
387
 
        data = response_handler.read_body_bytes()
388
 
        data_offset = 0
389
339
        for c_offset in coalesced:
390
340
            if len(data) < c_offset.length:
391
341
                raise errors.ShortReadvError(relpath, c_offset.start,
392
342
                            c_offset.length, actual=len(data))
393
343
            for suboffset, subsize in c_offset.ranges:
394
344
                key = (c_offset.start+suboffset, subsize)
395
 
                this_data = data[data_offset+suboffset:
396
 
                                 data_offset+suboffset+subsize]
397
 
                # Special case when the data is in-order, rather than packing
398
 
                # into a map and then back out again. Benchmarking shows that
399
 
                # this has 100% hit rate, but leave in the data_map work just
400
 
                # in case.
401
 
                # TODO: Could we get away with using buffer() to avoid the
402
 
                #       memory copy?  Callers would need to realize they may
403
 
                #       not have a real string.
404
 
                if key == cur_offset_and_size:
405
 
                    yield cur_offset_and_size[0], this_data
406
 
                    cur_offset_and_size = next_offset[0] = offset_stack.next()
407
 
                else:
408
 
                    data_map[key] = this_data
409
 
            data_offset += c_offset.length
 
345
                data_map[key] = data[suboffset:suboffset+subsize]
 
346
            data = data[c_offset.length:]
410
347
 
411
348
            # Now that we've read some data, see if we can yield anything back
412
349
            while cur_offset_and_size in data_map:
413
350
                this_data = data_map.pop(cur_offset_and_size)
414
351
                yield cur_offset_and_size[0], this_data
415
 
                cur_offset_and_size = next_offset[0] = offset_stack.next()
 
352
                cur_offset_and_size = offset_stack.next()
416
353
 
417
354
    def rename(self, rel_from, rel_to):
418
355
        self._call('rename',
427
364
    def rmdir(self, relpath):
428
365
        resp = self._call('rmdir', self._remote_path(relpath))
429
366
 
430
 
    def _ensure_ok(self, resp):
431
 
        if resp[0] != 'ok':
432
 
            raise errors.UnexpectedSmartServerResponse(resp)
433
 
 
434
 
    def _translate_error(self, err, relpath=None):
435
 
        remote._translate_error(err, path=relpath)
 
367
    def _translate_error(self, resp, orig_path=None):
 
368
        """Raise an exception from a response"""
 
369
        if resp is None:
 
370
            what = None
 
371
        else:
 
372
            what = resp[0]
 
373
        if what == 'ok':
 
374
            return
 
375
        elif what == 'NoSuchFile':
 
376
            if orig_path is not None:
 
377
                error_path = orig_path
 
378
            else:
 
379
                error_path = resp[1]
 
380
            raise errors.NoSuchFile(error_path)
 
381
        elif what == 'error':
 
382
            raise errors.SmartProtocolError(unicode(resp[1]))
 
383
        elif what == 'FileExists':
 
384
            raise errors.FileExists(resp[1])
 
385
        elif what == 'DirectoryNotEmpty':
 
386
            raise errors.DirectoryNotEmpty(resp[1])
 
387
        elif what == 'ShortReadvError':
 
388
            raise errors.ShortReadvError(resp[1], int(resp[2]),
 
389
                                         int(resp[3]), int(resp[4]))
 
390
        elif what in ('UnicodeEncodeError', 'UnicodeDecodeError'):
 
391
            encoding = str(resp[1]) # encoding must always be a string
 
392
            val = resp[2]
 
393
            start = int(resp[3])
 
394
            end = int(resp[4])
 
395
            reason = str(resp[5]) # reason must always be a string
 
396
            if val.startswith('u:'):
 
397
                val = val[2:].decode('utf-8')
 
398
            elif val.startswith('s:'):
 
399
                val = val[2:].decode('base64')
 
400
            if what == 'UnicodeDecodeError':
 
401
                raise UnicodeDecodeError(encoding, val, start, end, reason)
 
402
            elif what == 'UnicodeEncodeError':
 
403
                raise UnicodeEncodeError(encoding, val, start, end, reason)
 
404
        elif what == "ReadOnlyError":
 
405
            raise errors.TransportNotPossible('readonly transport')
 
406
        elif what == "ReadError":
 
407
            if orig_path is not None:
 
408
                error_path = orig_path
 
409
            else:
 
410
                error_path = resp[1]
 
411
            raise errors.ReadError(error_path)
 
412
        elif what == "PermissionDenied":
 
413
            if orig_path is not None:
 
414
                error_path = orig_path
 
415
            else:
 
416
                error_path = resp[1]
 
417
            raise errors.PermissionDenied(error_path)
 
418
        else:
 
419
            raise errors.SmartProtocolError('unexpected smart server error: %r' % (resp,))
436
420
 
437
421
    def disconnect(self):
438
422
        self.get_smart_medium().disconnect()
439
423
 
 
424
    def delete_tree(self, relpath):
 
425
        raise errors.TransportNotPossible('readonly transport')
 
426
 
440
427
    def stat(self, relpath):
441
428
        resp = self._call2('stat', self._remote_path(relpath))
442
429
        if resp[0] == 'stat':
443
430
            return _SmartStat(int(resp[1]), int(resp[2], 8))
444
 
        raise errors.UnexpectedSmartServerResponse(resp)
 
431
        else:
 
432
            self._translate_error(resp)
445
433
 
446
434
    ## def lock_read(self, relpath):
447
435
    ##     """Lock the given file for shared (read) access.
463
451
        resp = self._call2('list_dir', self._remote_path(relpath))
464
452
        if resp[0] == 'names':
465
453
            return [name.encode('ascii') for name in resp[1:]]
466
 
        raise errors.UnexpectedSmartServerResponse(resp)
 
454
        else:
 
455
            self._translate_error(resp)
467
456
 
468
457
    def iter_files_recursive(self):
469
458
        resp = self._call2('iter_files_recursive', self._remote_path(''))
470
459
        if resp[0] == 'names':
471
460
            return resp[1:]
472
 
        raise errors.UnexpectedSmartServerResponse(resp)
 
461
        else:
 
462
            self._translate_error(resp)
473
463
 
474
464
 
475
465
class RemoteTCPTransport(RemoteTransport):
476
466
    """Connection to smart server over plain tcp.
477
 
 
 
467
    
478
468
    This is essentially just a factory to get 'RemoteTransport(url,
479
469
        SmartTCPClientMedium).
480
470
    """
508
498
    """
509
499
 
510
500
    def _build_medium(self):
 
501
        # ssh will prompt the user for a password if needed and if none is
 
502
        # provided but it will not give it back, so no credentials can be
 
503
        # stored.
511
504
        location_config = config.LocationConfig(self.base)
512
505
        bzr_remote_path = location_config.get_bzr_remote_path()
513
 
        user = self._user
514
 
        if user is None:
515
 
            auth = config.AuthenticationConfig()
516
 
            user = auth.get_user('ssh', self._host, self._port)
517
506
        client_medium = medium.SmartSSHClientMedium(self._host, self._port,
518
 
            user, self._password, self.base,
 
507
            self._user, self._password, self.base,
519
508
            bzr_remote_path=bzr_remote_path)
520
 
        return client_medium, (user, self._password)
 
509
        return client_medium, None
521
510
 
522
511
 
523
512
class RemoteHTTPTransport(RemoteTransport):
524
513
    """Just a way to connect between a bzr+http:// url and http://.
525
 
 
 
514
    
526
515
    This connection operates slightly differently than the RemoteSSHTransport.
527
516
    It uses a plain http:// transport underneath, which defines what remote
528
517
    .bzr/smart URL we are connected to. From there, all paths that are sent are
577
566
                                   _from_transport=self,
578
567
                                   http_transport=self._http_transport)
579
568
 
580
 
    def _redirected_to(self, source, target):
581
 
        """See transport._redirected_to"""
582
 
        redirected = self._http_transport._redirected_to(source, target)
583
 
        if (redirected is not None
584
 
            and isinstance(redirected, type(self._http_transport))):
585
 
            return RemoteHTTPTransport('bzr+' + redirected.external_url(),
586
 
                                       http_transport=redirected)
587
 
        else:
588
 
            # Either None or a transport for a different protocol
589
 
            return redirected
590
 
 
591
 
 
592
 
class HintingSSHTransport(transport.Transport):
593
 
    """Simple transport that handles ssh:// and points out bzr+ssh://."""
594
 
 
595
 
    def __init__(self, url):
596
 
        raise errors.UnsupportedProtocol(url,
597
 
            'bzr supports bzr+ssh to operate over ssh, use "bzr+%s".' % url)
598
 
 
599
569
 
600
570
def get_test_permutations():
601
571
    """Return (transport, server) permutations for testing."""
602
572
    ### We may need a little more test framework support to construct an
603
573
    ### appropriate RemoteTransport in the future.
604
 
    from bzrlib.tests import test_server
605
 
    return [(RemoteTCPTransport, test_server.SmartTCPServer_for_testing)]
 
574
    from bzrlib.smart import server
 
575
    return [(RemoteTCPTransport, server.SmartTCPServer_for_testing)]