29
from cStringIO import StringIO
30
from collections import deque
31
from copy import deepcopy
33
from stat import S_ISDIR
33
from bzrlib.lazy_import import lazy_import
34
lazy_import(globals(), """
36
from stat import S_ISDIR
35
from unittest import TestSuite
49
from bzrlib.symbol_versioning import (
41
import bzrlib.errors as errors
42
from bzrlib.errors import DependencyNotPresent
43
import bzrlib.osutils as osutils
44
from bzrlib.osutils import pumpfile
45
from bzrlib.symbol_versioning import (deprecated_passed, deprecated_method, deprecated_function,
50
46
DEPRECATED_PARAMETER,
52
from bzrlib.trace import (
55
from bzrlib import registry
58
# a dictionary of open file streams. Keys are absolute paths, values are
48
from bzrlib.trace import mutter, warning
49
import bzrlib.urlutils as urlutils
51
# {prefix: [transport_classes]}
52
# Transports are inserted onto the list LIFO and tried in order; as a result
53
# transports provided by plugins are tried first, which is usually what we
55
_protocol_handlers = {
58
def register_transport(prefix, klass, override=DEPRECATED_PARAMETER):
59
"""Register a transport that can be used to open URLs
61
Normally you should use register_lazy_transport, which defers loading the
62
implementation until it's actually used, and so avoids pulling in possibly
63
large implementation libraries.
65
# Note that this code runs very early in library setup -- trace may not be
67
global _protocol_handlers
68
if deprecated_passed(override):
69
warnings.warn("register_transport(override) is deprecated")
70
_protocol_handlers.setdefault(prefix, []).insert(0, klass)
73
def register_lazy_transport(scheme, module, classname):
74
"""Register lazy-loaded transport class.
76
When opening a URL with the given scheme, load the module and then
77
instantiate the particular class.
79
If the module raises DependencyNotPresent when it's imported, it is
80
skipped and another implementation of the protocol is tried. This is
81
intended to be used when the implementation depends on an external
82
implementation that may not be present. If any other error is raised, it
83
propagates up and the attempt to open the url fails.
85
# TODO: If no implementation of a protocol is available because of missing
86
# dependencies, we should perhaps show the message about what dependency
89
mod = __import__(module, globals(), locals(), [classname])
90
klass = getattr(mod, classname)
92
_loader.module = module
93
register_transport(scheme, _loader)
63
96
def _get_protocol_handlers():
64
97
"""Return a dictionary of {urlprefix: [factory]}"""
65
return transport_list_registry
98
return _protocol_handlers
68
101
def _set_protocol_handlers(new_handlers):
71
104
WARNING this will remove all build in protocols. Use with care.
73
global transport_list_registry
74
transport_list_registry = new_handlers
106
global _protocol_handlers
107
_protocol_handlers = new_handlers
77
110
def _clear_protocol_handlers():
78
global transport_list_registry
79
transport_list_registry = TransportListRegistry()
111
global _protocol_handlers
112
_protocol_handlers = {}
82
115
def _get_transport_modules():
83
116
"""Return a list of the modules providing transports."""
85
for prefix, factory_list in transport_list_registry.items():
118
for prefix, factory_list in _protocol_handlers.items():
86
119
for factory in factory_list:
87
if hasattr(factory, "_module_name"):
88
modules.add(factory._module_name)
120
if factory.__module__ == "bzrlib.transport":
121
# this is a lazy load transport, because no real ones
122
# are directlry in bzrlib.transport
123
modules.add(factory.module)
90
modules.add(factory._obj.__module__)
91
# Add chroot and pathfilter directly, because there is no handler
93
modules.add('bzrlib.transport.chroot')
94
modules.add('bzrlib.transport.pathfilter')
125
modules.add(factory.__module__)
95
126
result = list(modules)
100
class TransportListRegistry(registry.Registry):
101
"""A registry which simplifies tracking available Transports.
103
A registration of a new protocol requires two steps:
104
1) register the prefix with the function register_transport( )
105
2) register the protocol provider with the function
106
register_transport_provider( ) ( and the "lazy" variant )
108
This is needed because:
109
a) a single provider can support multiple protocols ( like the ftp
110
provider which supports both the ftp:// and the aftp:// protocols )
111
b) a single protocol can have multiple providers ( like the http://
112
protocol which is supported by both the urllib and pycurl provider )
115
def register_transport_provider(self, key, obj):
116
self.get(key).insert(0, registry._ObjectGetter(obj))
118
def register_lazy_transport_provider(self, key, module_name, member_name):
119
self.get(key).insert(0,
120
registry._LazyObjectGetter(module_name, member_name))
122
def register_transport(self, key, help=None):
123
self.register(key, [], help)
125
def set_default_transport(self, key=None):
126
"""Return either 'key' or the default key if key is None"""
127
self._default_key = key
130
transport_list_registry = TransportListRegistry()
133
def register_transport_proto(prefix, help=None, info=None,
134
register_netloc=False):
135
transport_list_registry.register_transport(prefix, help)
137
if not prefix.endswith('://'):
138
raise ValueError(prefix)
139
register_urlparse_netloc_protocol(prefix[:-3])
142
def register_lazy_transport(prefix, module, classname):
143
if not prefix in transport_list_registry:
144
register_transport_proto(prefix)
145
transport_list_registry.register_lazy_transport_provider(prefix, module, classname)
148
def register_transport(prefix, klass, override=DEPRECATED_PARAMETER):
149
if not prefix in transport_list_registry:
150
register_transport_proto(prefix)
151
transport_list_registry.register_transport_provider(prefix, klass)
154
131
def register_urlparse_netloc_protocol(protocol):
155
132
"""Ensure that protocol is setup to be used with urlparse netloc parsing."""
156
133
if protocol not in urlparse.uses_netloc:
157
134
urlparse.uses_netloc.append(protocol)
160
def _unregister_urlparse_netloc_protocol(protocol):
161
"""Remove protocol from urlparse netloc parsing.
163
Except for tests, you should never use that function. Using it with 'http',
164
for example, will break all http transports.
166
if protocol in urlparse.uses_netloc:
167
urlparse.uses_netloc.remove(protocol)
170
def unregister_transport(scheme, factory):
171
"""Unregister a transport."""
172
l = transport_list_registry.get(scheme)
176
transport_list_registry.get(scheme).remove(i)
179
transport_list_registry.remove(scheme)
138
# TODO: jam 20060606 urls should only be ascii, or they should raise InvalidURL
139
if isinstance(url, unicode):
140
url = url.encode('utf-8')
141
(scheme, netloc, path, params,
142
query, fragment) = urlparse.urlparse(url, allow_fragments=False)
143
username = password = host = port = None
145
username, host = netloc.split('@', 1)
147
username, password = username.split(':', 1)
148
password = urllib.unquote(password)
149
username = urllib.unquote(username)
154
host, port = host.rsplit(':', 1)
158
# TODO: Should this be ConnectionError?
159
raise errors.TransportError('%s: invalid port number' % port)
160
host = urllib.unquote(host)
162
path = urllib.unquote(path)
164
return (scheme, username, password, host, port, path)
182
167
class _CoalescedOffset(object):
193
178
return cmp((self.start, self.length, self.ranges),
194
179
(other.start, other.length, other.ranges))
197
return '%s(%r, %r, %r)' % (self.__class__.__name__,
198
self.start, self.length, self.ranges)
201
class LateReadError(object):
202
"""A helper for transports which pretends to be a readable file.
204
When read() is called, errors.ReadError is raised.
207
def __init__(self, path):
211
"""a no-op - do nothing."""
214
"""Raise ReadError."""
215
raise errors.ReadError(self._path)
220
def read(self, count=-1):
227
class FileStream(object):
228
"""Base class for FileStreams."""
230
def __init__(self, transport, relpath):
231
"""Create a FileStream for relpath on transport."""
232
self.transport = transport
233
self.relpath = relpath
236
"""A hook point for subclasses that need to take action on close."""
240
del _file_streams[self.transport.abspath(self.relpath)]
243
class FileFileStream(FileStream):
244
"""A file stream object returned by open_write_stream.
246
This version uses a file like object to perform writes.
249
def __init__(self, transport, relpath, file_handle):
250
FileStream.__init__(self, transport, relpath)
251
self.file_handle = file_handle
254
self.file_handle.close()
256
def write(self, bytes):
257
osutils.pump_string_file(bytes, self.file_handle)
260
class AppendBasedFileStream(FileStream):
261
"""A file stream object returned by open_write_stream.
263
This version uses append on a transport to perform writes.
266
def write(self, bytes):
267
self.transport.append_bytes(self.relpath, bytes)
270
182
class Transport(object):
271
183
"""This class encapsulates methods for retrieving or putting a file
272
184
from/to a storage location.
274
186
Most functions have a _multi variant, which allows you to queue up
275
multiple requests. They generally have a dumb base implementation
187
multiple requests. They generally have a dumb base implementation
276
188
which just iterates over the arguments, but smart Transport
277
189
implementations can do pipelining.
278
190
In general implementations should support having a generator or a list
279
191
as an argument (ie always iterate, never index)
281
:ivar base: Base URL for the transport; should always end in a slash.
284
194
# implementations can override this if it is more efficient
324
234
def clone(self, offset=None):
325
235
"""Return a new Transport object, cloned from the current location,
326
using a subdirectory or parent directory. This allows connections
236
using a subdirectory or parent directory. This allows connections
327
237
to be pooled, rather than a new one needed for each subdir.
329
239
raise NotImplementedError(self.clone)
331
def create_prefix(self):
332
"""Create all the directories leading down to self.base."""
334
needed = [cur_transport]
335
# Recurse upwards until we can create a directory successfully
337
new_transport = cur_transport.clone('..')
338
if new_transport.base == cur_transport.base:
339
raise errors.BzrCommandError(
340
"Failed to create path prefix for %s."
341
% cur_transport.base)
343
new_transport.mkdir('.')
344
except errors.NoSuchFile:
345
needed.append(new_transport)
346
cur_transport = new_transport
347
except errors.FileExists:
351
# Now we only need to create child directories
353
cur_transport = needed.pop()
354
cur_transport.ensure_base()
356
def ensure_base(self):
357
"""Ensure that the directory this transport references exists.
359
This will create a directory if it doesn't exist.
360
:return: True if the directory was created, False otherwise.
362
# The default implementation just uses "Easier to ask for forgiveness
363
# than permission". We attempt to create the directory, and just
364
# suppress FileExists and PermissionDenied (for Windows) exceptions.
367
except (errors.FileExists, errors.PermissionDenied):
372
def external_url(self):
373
"""Return a URL for self that can be given to an external process.
375
There is no guarantee that the URL can be accessed from a different
376
machine - e.g. file:/// urls are only usable on the local machine,
377
sftp:/// urls when the server is only bound to localhost are only
378
usable from localhost etc.
380
NOTE: This method may remove security wrappers (e.g. on chroot
381
transports) and thus should *only* be used when the result will not
382
be used to obtain a new transport within bzrlib. Ideally chroot
383
transports would know enough to cause the external url to be the exact
384
one used that caused the chrooting in the first place, but that is not
387
:return: A URL that can be given to another process.
388
:raises InProcessTransport: If the transport is one that cannot be
389
accessed out of the current process (e.g. a MemoryTransport)
390
then InProcessTransport is raised.
392
raise NotImplementedError(self.external_url)
241
def should_cache(self):
242
"""Return True if the data pulled across should be cached locally.
394
246
def _pump(self, from_file, to_file):
395
"""Most children will need to copy from one file-like
247
"""Most children will need to copy from one file-like
396
248
object or string to another one.
397
249
This just gives them something easy to call.
399
return osutils.pumpfile(from_file, to_file)
251
if isinstance(from_file, basestring):
252
to_file.write(from_file)
254
pumpfile(from_file, to_file)
401
256
def _get_total(self, multi):
402
257
"""Try to figure out how many entries are in multi,
454
297
def abspath(self, relpath):
455
298
"""Return the full url to the given relative path.
299
This can be supplied with a string or a list
457
:param relpath: a string of a relative path
301
XXX: Robert Collins 20051016 - is this really needed in the public
460
# XXX: Robert Collins 20051016 - is this really needed in the public
462
304
raise NotImplementedError(self.abspath)
464
def _combine_paths(self, base_path, relpath):
465
"""Transform a Transport-relative path to a remote absolute path.
467
This does not handle substitution of ~ but does handle '..' and '.'
472
t._combine_paths('/home/sarah', 'project/foo')
473
=> '/home/sarah/project/foo'
474
t._combine_paths('/home/sarah', '../../etc')
476
t._combine_paths('/home/sarah', '/etc')
479
:param base_path: urlencoded path for the transport root; typically a
480
URL but need not contain scheme/host/etc.
481
:param relpath: relative url string for relative part of remote path.
482
:return: urlencoded string for final path.
484
if not isinstance(relpath, str):
485
raise errors.InvalidURL(relpath)
486
if relpath.startswith('/'):
489
base_parts = base_path.split('/')
490
if len(base_parts) > 0 and base_parts[-1] == '':
491
base_parts = base_parts[:-1]
492
for p in relpath.split('/'):
494
if len(base_parts) == 0:
495
# In most filesystems, a request for the parent
496
# of root, just returns root.
503
path = '/'.join(base_parts)
504
if not path.startswith('/'):
508
def recommended_page_size(self):
509
"""Return the recommended page size for this transport.
511
This is potentially different for every path in a given namespace.
512
For example, local transports might use an operating system call to
513
get the block size for a given path, which can vary due to mount
516
:return: The page size in bytes.
520
306
def relpath(self, abspath):
521
307
"""Return the local path portion from a given absolute path.
523
309
This default implementation is not suitable for filesystems with
524
aliasing, such as that given by symlinks, where a path may not
525
start with our base, but still be a relpath once aliasing is
310
aliasing, such as that given by symlinks, where a path may not
311
start with our base, but still be a relpath once aliasing is
528
314
# TODO: This might want to use bzrlib.osutils.relpath
586
369
def get(self, relpath):
587
370
"""Get the file at the given relative path.
589
This may fail in a number of ways:
590
- HTTP servers may return content for a directory. (unexpected
592
- FTP servers may indicate NoSuchFile for a directory.
593
- SFTP servers may give a file handle for a directory that will
596
For correct use of the interface, be sure to catch errors.PathError
597
when calling it and catch errors.ReadError when reading from the
600
372
:param relpath: The relative path to the file
601
:rtype: File-like object.
603
374
raise NotImplementedError(self.get)
605
def get_bytes(self, relpath):
606
"""Get a raw string of the bytes for a file at the given location.
608
:param relpath: The relative path to the file
610
f = self.get(relpath)
616
def get_smart_medium(self):
617
"""Return a smart client medium for this transport if possible.
619
A smart medium doesn't imply the presence of a smart server: it implies
620
that the smart protocol can be tunnelled via this transport.
622
:raises NoSmartMedium: if no smart server medium is available.
624
raise errors.NoSmartMedium(self)
626
def readv(self, relpath, offsets, adjust_for_latency=False,
628
"""Get parts of the file at the given relative path.
630
:param relpath: The path to read data from.
631
:param offsets: A list of (offset, size) tuples.
632
:param adjust_for_latency: Adjust the requested offsets to accomodate
633
transport latency. This may re-order the offsets, expand them to
634
grab adjacent data when there is likely a high cost to requesting
635
data relative to delivering it.
636
:param upper_limit: When adjust_for_latency is True setting upper_limit
637
allows the caller to tell the transport about the length of the
638
file, so that requests are not issued for ranges beyond the end of
639
the file. This matters because some servers and/or transports error
640
in such a case rather than just satisfying the available ranges.
641
upper_limit should always be provided when adjust_for_latency is
642
True, and should be the size of the file in bytes.
643
:return: A list or generator of (offset, data) tuples
645
if adjust_for_latency:
646
# Design note: We may wish to have different algorithms for the
647
# expansion of the offsets per-transport. E.g. for local disk to
648
# use page-aligned expansion. If that is the case consider the
649
# following structure:
650
# - a test that transport.readv uses self._offset_expander or some
651
# similar attribute, to do the expansion
652
# - a test for each transport that it has some known-good offset
654
# - unit tests for each offset expander
655
# - a set of tests for the offset expander interface, giving
656
# baseline behaviour (which the current transport
657
# adjust_for_latency tests could be repurposed to).
658
offsets = self._sort_expand_and_combine(offsets, upper_limit)
659
return self._readv(relpath, offsets)
661
def _readv(self, relpath, offsets):
662
"""Get parts of the file at the given relative path.
664
:param relpath: The path to read.
665
:param offsets: A list of (offset, size) tuples.
376
def readv(self, relpath, offsets):
377
"""Get parts of the file at the given relative path.
379
:offsets: A list of (offset, size) tuples.
666
380
:return: A list or generator of (offset, data) tuples
671
385
fp = self.get(relpath)
672
return self._seek_and_read(fp, offsets, relpath)
386
return self._seek_and_read(fp, offsets)
674
def _seek_and_read(self, fp, offsets, relpath='<unknown>'):
388
def _seek_and_read(self, fp, offsets):
675
389
"""An implementation of readv that uses fp.seek and fp.read.
677
391
This uses _coalesce_offsets to issue larger reads and fewer seeks.
709
420
# Now that we've read some data, see if we can yield anything back
710
421
while cur_offset_and_size in data_map:
711
422
this_data = data_map.pop(cur_offset_and_size)
712
this_offset = cur_offset_and_size[0]
714
cur_offset_and_size = offset_stack.next()
715
except StopIteration:
716
# Close the file handle as there will be no more data
717
# The handle would normally be cleaned up as this code goes
718
# out of scope, but as we are a generator, not all code
719
# will re-enter once we have consumed all the expected
721
# zip(range(len(requests)), readv(foo, requests))
722
# Will stop because the range is done, and not run the
723
# cleanup code for the readv().
725
cur_offset_and_size = None
726
yield this_offset, this_data
728
def _sort_expand_and_combine(self, offsets, upper_limit):
731
:param offsets: A readv vector - (offset, length) tuples.
732
:param upper_limit: The highest byte offset that may be requested.
733
:return: A readv vector that will read all the regions requested by
734
offsets, in start-to-end order, with no duplicated regions,
735
expanded by the transports recommended page size.
737
offsets = sorted(offsets)
738
# short circuit empty requests
739
if len(offsets) == 0:
741
# Quick thunk to stop this function becoming a generator
742
# itself, rather we return a generator that has nothing to
746
return empty_yielder()
747
# expand by page size at either end
748
maximum_expansion = self.recommended_page_size()
750
for offset, length in offsets:
751
expansion = maximum_expansion - length
753
# we're asking for more than the minimum read anyway.
755
reduction = expansion / 2
756
new_offset = offset - reduction
757
new_length = length + expansion
759
# don't ask for anything < 0
761
if (upper_limit is not None and
762
new_offset + new_length > upper_limit):
763
new_length = upper_limit - new_offset
764
new_offsets.append((new_offset, new_length))
765
# combine the expanded offsets
767
current_offset, current_length = new_offsets[0]
768
current_finish = current_length + current_offset
769
for offset, length in new_offsets[1:]:
770
finish = offset + length
771
if offset > current_finish:
772
# there is a gap, output the current accumulator and start
773
# a new one for the region we're examining.
774
offsets.append((current_offset, current_length))
775
current_offset = offset
776
current_length = length
777
current_finish = finish
779
if finish > current_finish:
780
# extend the current accumulator to the end of the region
782
current_finish = finish
783
current_length = finish - current_offset
784
offsets.append((current_offset, current_length))
423
yield cur_offset_and_size[0], this_data
424
cur_offset_and_size = offset_stack.next()
788
def _coalesce_offsets(offsets, limit=0, fudge_factor=0, max_size=0):
427
def _coalesce_offsets(offsets, limit, fudge_factor):
789
428
"""Yield coalesced offsets.
791
430
With a long list of neighboring requests, combine them
792
431
into a single large request, while retaining the original
794
433
Turns [(15, 10), (25, 10)] => [(15, 20, [(0, 10), (10, 10)])]
795
Note that overlapping requests are not permitted. (So [(15, 10), (20,
796
10)] will raise a ValueError.) This is because the data we access never
797
overlaps, and it allows callers to trust that we only need any byte of
798
data for 1 request (so nothing needs to be buffered to fulfill a second
801
435
:param offsets: A list of (start, length) pairs
802
:param limit: Only combine a maximum of this many pairs Some transports
803
penalize multiple reads more than others, and sometimes it is
804
better to return early.
436
:param limit: Only combine a maximum of this many pairs
437
Some transports penalize multiple reads more than
438
others, and sometimes it is better to return early.
806
440
:param fudge_factor: All transports have some level of 'it is
807
better to read some more data and throw it away rather
441
better to read some more data and throw it away rather
808
442
than seek', so collapse if we are 'close enough'
809
:param max_size: Create coalesced offsets no bigger than this size.
810
When a single offset is bigger than 'max_size', it will keep
811
its size and be alone in the coalesced offset.
812
0 means no maximum size.
813
:return: return a list of _CoalescedOffset objects, which have members
814
for where to start, how much to read, and how to split those chunks
443
:return: yield _CoalescedOffset objects, which have members for wher
444
to start, how much to read, and how to split those
818
448
cur = _CoalescedOffset(None, None, [])
819
coalesced_offsets = []
822
# 'unlimited', but we actually take this to mean 100MB buffer limit
823
max_size = 100*1024*1024
825
450
for start, size in offsets:
826
451
end = start + size
827
if (last_end is not None
452
if (last_end is not None
828
453
and start <= last_end + fudge_factor
829
454
and start >= cur.start
830
and (limit <= 0 or len(cur.ranges) < limit)
831
and (max_size <= 0 or end - cur.start <= max_size)):
833
raise ValueError('Overlapping range not allowed:'
834
' last range ended at %s, new one starts at %s'
455
and (limit <= 0 or len(cur.ranges) < limit)):
836
456
cur.length = end - cur.start
837
457
cur.ranges.append((start-cur.start, size))
839
459
if cur.start is not None:
840
coalesced_offsets.append(cur)
841
461
cur = _CoalescedOffset(start, size, [(0, size)])
844
464
if cur.start is not None:
845
coalesced_offsets.append(cur)
846
return coalesced_offsets
848
469
def get_multi(self, relpaths, pb=None):
849
470
"""Get a list of file-like objects, one for each entry in relpaths.
851
472
:param relpaths: A list of relative paths.
852
:param pb: An optional ProgressTask for indicating percent done.
473
:param pb: An optional ProgressBar for indicating percent done.
853
474
:return: A list or generator of file-like objects
855
476
# TODO: Consider having this actually buffer the requests,
862
483
yield self.get(relpath)
865
def put_bytes(self, relpath, bytes, mode=None):
866
"""Atomically put the supplied bytes into the given location.
868
:param relpath: The location to put the contents, relative to the
870
:param bytes: A bytestring of data.
871
:param mode: Create the file with the given mode.
874
if not isinstance(bytes, str):
875
raise AssertionError(
876
'bytes must be a plain string, not %s' % type(bytes))
877
return self.put_file(relpath, StringIO(bytes), mode=mode)
879
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
880
create_parent_dir=False,
882
"""Copy the string into the target location.
884
This function is not strictly safe to use. See
885
Transport.put_bytes_non_atomic for more information.
887
:param relpath: The remote location to put the contents.
888
:param bytes: A string object containing the raw bytes to write into
890
:param mode: Possible access permissions for new file.
891
None means do not set remote permissions.
892
:param create_parent_dir: If we cannot create the target file because
893
the parent directory does not exist, go ahead and
894
create it, and then try again.
895
:param dir_mode: Possible access permissions for new directories.
897
if not isinstance(bytes, str):
898
raise AssertionError(
899
'bytes must be a plain string, not %s' % type(bytes))
900
self.put_file_non_atomic(relpath, StringIO(bytes), mode=mode,
901
create_parent_dir=create_parent_dir,
904
def put_file(self, relpath, f, mode=None):
905
"""Copy the file-like object into the location.
486
def put(self, relpath, f, mode=None):
487
"""Copy the file-like or string object into the location.
907
489
:param relpath: Location to put the contents, relative to base.
908
:param f: File-like object.
909
:param mode: The mode for the newly created file,
910
None means just use the default.
911
:return: The length of the file that was written.
913
# We would like to mark this as NotImplemented, but most likely
914
# transports have defined it in terms of the old api.
915
symbol_versioning.warn('Transport %s should implement put_file,'
916
' rather than implementing put() as of'
918
% (self.__class__.__name__,),
920
return self.put(relpath, f, mode=mode)
921
#raise NotImplementedError(self.put_file)
923
def put_file_non_atomic(self, relpath, f, mode=None,
924
create_parent_dir=False,
926
"""Copy the file-like object into the target location.
928
This function is not strictly safe to use. It is only meant to
929
be used when you already know that the target does not exist.
930
It is not safe, because it will open and truncate the remote
931
file. So there may be a time when the file has invalid contents.
933
:param relpath: The remote location to put the contents.
934
:param f: File-like object.
935
:param mode: Possible access permissions for new file.
936
None means do not set remote permissions.
937
:param create_parent_dir: If we cannot create the target file because
938
the parent directory does not exist, go ahead and
939
create it, and then try again.
940
:param dir_mode: Possible access permissions for new directories.
942
# Default implementation just does an atomic put.
944
return self.put_file(relpath, f, mode=mode)
945
except errors.NoSuchFile:
946
if not create_parent_dir:
948
parent_dir = osutils.dirname(relpath)
950
self.mkdir(parent_dir, mode=dir_mode)
951
return self.put_file(relpath, f, mode=mode)
490
:param f: File-like or string object.
491
:param mode: The mode for the newly created file,
492
None means just use the default
494
raise NotImplementedError(self.put)
496
def put_multi(self, files, mode=None, pb=None):
497
"""Put a set of files into the location.
499
:param files: A list of tuples of relpath, file object [(path1, file1), (path2, file2),...]
500
:param pb: An optional ProgressBar for indicating percent done.
501
:param mode: The mode for the newly created files
502
:return: The number of files copied.
505
self.put(path, f, mode=mode)
506
return len(self._iterate_over(files, put, pb, 'put', expand=True))
953
508
def mkdir(self, relpath, mode=None):
954
509
"""Create a directory at the given path."""
960
515
self.mkdir(path, mode=mode)
961
516
return len(self._iterate_over(relpaths, mkdir, pb, 'mkdir', expand=False))
963
def open_write_stream(self, relpath, mode=None):
964
"""Open a writable file stream at relpath.
966
A file stream is a file like object with a write() method that accepts
967
bytes to write.. Buffering may occur internally until the stream is
968
closed with stream.close(). Calls to readv or the get_* methods will
969
be synchronised with any internal buffering that may be present.
971
:param relpath: The relative path to the file.
972
:param mode: The mode for the newly created file,
973
None means just use the default
974
:return: A FileStream. FileStream objects have two methods, write() and
975
close(). There is no guarantee that data is committed to the file
976
if close() has not been called (even if get() is called on the same
979
raise NotImplementedError(self.open_write_stream)
981
def append_file(self, relpath, f, mode=None):
982
"""Append bytes from a file-like object to a file at relpath.
984
The file is created if it does not already exist.
986
:param f: a file-like object of the bytes to append.
987
:param mode: Unix mode for newly created files. This is not used for
990
:returns: the length of relpath before the content was written to it.
992
symbol_versioning.warn('Transport %s should implement append_file,'
993
' rather than implementing append() as of'
995
% (self.__class__.__name__,),
997
return self.append(relpath, f, mode=mode)
999
def append_bytes(self, relpath, bytes, mode=None):
1000
"""Append bytes to a file at relpath.
1002
The file is created if it does not already exist.
1005
:param f: a string of the bytes to append.
1006
:param mode: Unix mode for newly created files. This is not used for
1009
:returns: the length of relpath before the content was written to it.
1011
if not isinstance(bytes, str):
1013
'bytes must be a plain string, not %s' % type(bytes))
1014
return self.append_file(relpath, StringIO(bytes), mode=mode)
518
def append(self, relpath, f):
519
"""Append the text in the file-like or string object to
520
the supplied location.
522
returns the length of f before the content was written to it.
524
raise NotImplementedError(self.append)
1016
526
def append_multi(self, files, pb=None):
1017
527
"""Append the text in each file-like or string object to
1018
528
the supplied location.
1020
530
:param files: A set of (path, f) entries
1021
:param pb: An optional ProgressTask for indicating percent done.
531
:param pb: An optional ProgressBar for indicating percent done.
1023
return self._iterate_over(files, self.append_file, pb, 'append', expand=True)
533
return self._iterate_over(files, self.append, pb, 'append', expand=True)
1025
535
def copy(self, rel_from, rel_to):
1026
536
"""Copy the item at rel_from to the location at rel_to.
1028
Override this for efficiency if a specific transport can do it
538
Override this for efficiency if a specific transport can do it
1029
539
faster than this default implementation.
1031
self.put_file(rel_to, self.get(rel_from))
541
self.put(rel_to, self.get(rel_from))
1033
543
def copy_multi(self, relpaths, pb=None):
1034
544
"""Copy a bunch of entries.
1036
546
:param relpaths: A list of tuples of the form [(from, to), (from, to),...]
1038
548
# This is the non-pipelined implementation, so that
1268
742
"""Return true if this transport can store and retrieve unix modebits.
1270
744
(For example, 0700 to make a directory owner-private.)
1272
Note: most callers will not want to switch on this, but should rather
746
Note: most callers will not want to switch on this, but should rather
1273
747
just try and set permissions and let them be either stored or not.
1274
748
This is intended mainly for the use of the test suite.
1276
Warning: this is not guaranteed to be accurate as sometimes we can't
750
Warning: this is not guaranteed to be accurate as sometimes we can't
1277
751
be sure: for example with vfat mounted on unix, or a windows sftp
1279
753
# TODO: Perhaps return a e.g. TransportCharacteristics that can answer
1280
754
# several questions about the transport.
1283
def _reuse_for(self, other_base):
1284
# This is really needed for ConnectedTransport only, but it's easier to
1285
# have Transport refuses to be reused than testing that the reuse
1286
# should be asked to ConnectedTransport only.
1289
def disconnect(self):
1290
# This is really needed for ConnectedTransport only, but it's easier to
1291
# have Transport do nothing than testing that the disconnect should be
1292
# asked to ConnectedTransport only.
1295
def _redirected_to(self, source, target):
1296
"""Returns a transport suitable to re-issue a redirected request.
1298
:param source: The source url as returned by the server.
1299
:param target: The target url as returned by the server.
1301
The redirection can be handled only if the relpath involved is not
1302
renamed by the redirection.
1304
:returns: A transport or None.
1306
# This returns None by default, meaning the transport can't handle the
1312
class _SharedConnection(object):
1313
"""A connection shared between several transports."""
1315
def __init__(self, connection=None, credentials=None, base=None):
1318
:param connection: An opaque object specific to each transport.
1320
:param credentials: An opaque object containing the credentials used to
1321
create the connection.
1323
self.connection = connection
1324
self.credentials = credentials
1328
class ConnectedTransport(Transport):
1329
"""A transport connected to a remote server.
1331
This class provide the basis to implement transports that need to connect
1334
Host and credentials are available as private attributes, cloning preserves
1335
them and share the underlying, protocol specific, connection.
1338
def __init__(self, base, _from_transport=None):
1341
The caller should ensure that _from_transport points at the same host
1344
:param base: transport root URL
1346
:param _from_transport: optional transport to build from. The built
1347
transport will share the connection with this transport.
1349
if not base.endswith('/'):
1352
self._user, self._password,
1353
self._host, self._port,
1354
self._path) = self._split_url(base)
1355
if _from_transport is not None:
1356
# Copy the password as it does not appear in base and will be lost
1357
# otherwise. It can appear in the _split_url above if the user
1358
# provided it on the command line. Otherwise, daughter classes will
1359
# prompt the user for one when appropriate.
1360
self._password = _from_transport._password
1362
base = self._unsplit_url(self._scheme,
1363
self._user, self._password,
1364
self._host, self._port,
1367
super(ConnectedTransport, self).__init__(base)
1368
if _from_transport is None:
1369
self._shared_connection = _SharedConnection()
1371
self._shared_connection = _from_transport._shared_connection
1373
def clone(self, offset=None):
1374
"""Return a new transport with root at self.base + offset
1376
We leave the daughter classes take advantage of the hint
1377
that it's a cloning not a raw creation.
1380
return self.__class__(self.base, _from_transport=self)
1382
return self.__class__(self.abspath(offset), _from_transport=self)
1385
def _split_url(url):
1386
return urlutils.parse_url(url)
1389
def _unsplit_url(scheme, user, password, host, port, path):
1391
Build the full URL for the given already URL encoded path.
1393
user, password, host and path will be quoted if they contain reserved
1396
:param scheme: protocol
1400
:param password: associated password
1402
:param host: the server address
1404
:param port: the associated port
1406
:param path: the absolute path on the server
1408
:return: The corresponding URL.
1410
netloc = urllib.quote(host)
1411
if user is not None:
1412
# Note that we don't put the password back even if we
1413
# have one so that it doesn't get accidentally
1415
netloc = '%s@%s' % (urllib.quote(user), netloc)
1416
if port is not None:
1417
netloc = '%s:%d' % (netloc, port)
1418
path = urlutils.escape(path)
1419
return urlparse.urlunparse((scheme, netloc, path, None, None, None))
1421
def relpath(self, abspath):
1422
"""Return the local path portion from a given absolute path"""
1423
scheme, user, password, host, port, path = self._split_url(abspath)
1425
if (scheme != self._scheme):
1426
error.append('scheme mismatch')
1427
if (user != self._user):
1428
error.append('user name mismatch')
1429
if (host != self._host):
1430
error.append('host mismatch')
1431
if (port != self._port):
1432
error.append('port mismatch')
1433
if not (path == self._path[:-1] or path.startswith(self._path)):
1434
error.append('path mismatch')
1436
extra = ', '.join(error)
1437
raise errors.PathNotChild(abspath, self.base, extra=extra)
1438
pl = len(self._path)
1439
return path[pl:].strip('/')
1441
def abspath(self, relpath):
1442
"""Return the full url to the given relative path.
1444
:param relpath: the relative path urlencoded
1446
:returns: the Unicode version of the absolute path for relpath.
1448
relative = urlutils.unescape(relpath).encode('utf-8')
1449
path = self._combine_paths(self._path, relative)
1450
return self._unsplit_url(self._scheme, self._user, self._password,
1451
self._host, self._port,
1454
def _remote_path(self, relpath):
1455
"""Return the absolute path part of the url to the given relative path.
1457
This is the path that the remote server expect to receive in the
1458
requests, daughter classes should redefine this method if needed and
1459
use the result to build their requests.
1461
:param relpath: the path relative to the transport base urlencoded.
1463
:return: the absolute Unicode path on the server,
1465
relative = urlutils.unescape(relpath).encode('utf-8')
1466
remote_path = self._combine_paths(self._path, relative)
1469
def _get_shared_connection(self):
1470
"""Get the object shared amongst cloned transports.
1472
This should be used only by classes that needs to extend the sharing
1473
with objects other than transports.
1475
Use _get_connection to get the connection itself.
1477
return self._shared_connection
1479
def _set_connection(self, connection, credentials=None):
1480
"""Record a newly created connection with its associated credentials.
1482
Note: To ensure that connection is still shared after a temporary
1483
failure and a new one needs to be created, daughter classes should
1484
always call this method to set the connection and do so each time a new
1485
connection is created.
1487
:param connection: An opaque object representing the connection used by
1490
:param credentials: An opaque object representing the credentials
1491
needed to create the connection.
1493
self._shared_connection.connection = connection
1494
self._shared_connection.credentials = credentials
1496
def _get_connection(self):
1497
"""Returns the transport specific connection object."""
1498
return self._shared_connection.connection
1500
def _get_credentials(self):
1501
"""Returns the credentials used to establish the connection."""
1502
return self._shared_connection.credentials
1504
def _update_credentials(self, credentials):
1505
"""Update the credentials of the current connection.
1507
Some protocols can renegociate the credentials within a connection,
1508
this method allows daughter classes to share updated credentials.
1510
:param credentials: the updated credentials.
1512
# We don't want to call _set_connection here as we are only updating
1513
# the credentials not creating a new connection.
1514
self._shared_connection.credentials = credentials
1516
def _reuse_for(self, other_base):
1517
"""Returns a transport sharing the same connection if possible.
1519
Note: we share the connection if the expected credentials are the
1520
same: (host, port, user). Some protocols may disagree and redefine the
1521
criteria in daughter classes.
1523
Note: we don't compare the passwords here because other_base may have
1524
been obtained from an existing transport.base which do not mention the
1527
:param other_base: the URL we want to share the connection with.
1529
:return: A new transport or None if the connection cannot be shared.
1532
(scheme, user, password,
1533
host, port, path) = self._split_url(other_base)
1534
except errors.InvalidURL:
1535
# No hope in trying to reuse an existing transport for an invalid
1540
# Don't compare passwords, they may be absent from other_base or from
1541
# self and they don't carry more information than user anyway.
1542
if (scheme == self._scheme
1543
and user == self._user
1544
and host == self._host
1545
and port == self._port):
1546
if not path.endswith('/'):
1547
# This normally occurs at __init__ time, but it's easier to do
1548
# it now to avoid creating two transports for the same base.
1550
if self._path == path:
1551
# shortcut, it's really the same transport
1553
# We don't call clone here because the intent is different: we
1554
# build a new transport on a different base (which may be totally
1555
# unrelated) but we share the connection.
1556
transport = self.__class__(other_base, _from_transport=self)
1559
def disconnect(self):
1560
"""Disconnect the transport.
1562
If and when required the transport willl reconnect automatically.
1564
raise NotImplementedError(self.disconnect)
1567
def get_transport(base, possible_transports=None):
758
# jam 20060426 For compatibility we copy the functions here
759
# TODO: The should be marked as deprecated
760
urlescape = urlutils.escape
761
urlunescape = urlutils.unescape
762
_urlRE = re.compile(r'^(?P<proto>[^:/\\]+)://(?P<path>.*)$')
765
def get_transport(base):
1568
766
"""Open a transport to access a URL or directory.
1570
:param base: either a URL or a directory name.
1572
:param transports: optional reusable transports list. If not None, created
1573
transports will be added to the list.
1575
:return: A new transport optionally sharing its connection with one of
1576
possible_transports.
768
base is either a URL or a directory name.
770
# TODO: give a better error if base looks like a url but there's no
771
# handler for the scheme?
772
global _protocol_handlers
1578
773
if base is None:
1581
from bzrlib.directory_service import directories
1582
base = directories.dereference(base)
1584
778
def convert_path_to_url(base, error_str):
1585
if urlutils.is_url(base):
1586
# This looks like a URL, but we weren't able to
779
m = _urlRE.match(base)
781
# This looks like a URL, but we weren't able to
1587
782
# instantiate it as such raise an appropriate error
1588
# FIXME: we have a 'error_str' unused and we use last_err below
1589
783
raise errors.UnsupportedProtocol(base, last_err)
1590
784
# This doesn't look like a protocol, consider it a local path
1591
785
new_base = urlutils.local_path_to_url(base)
1592
# mutter('converting os path %r => url %s', base, new_base)
786
mutter('converting os path %r => url %s', base, new_base)
1595
789
# Catch any URLs which are passing Unicode rather than ASCII
1644
821
return None, last_err
1647
def do_catching_redirections(action, transport, redirected):
1648
"""Execute an action with given transport catching redirections.
1650
This is a facility provided for callers needing to follow redirections
1651
silently. The silence is relative: it is the caller responsability to
1652
inform the user about each redirection or only inform the user of a user
1653
via the exception parameter.
1655
:param action: A callable, what the caller want to do while catching
1657
:param transport: The initial transport used.
1658
:param redirected: A callable receiving the redirected transport and the
1659
RedirectRequested exception.
1661
:return: Whatever 'action' returns
1663
MAX_REDIRECTIONS = 8
1665
# If a loop occurs, there is little we can do. So we don't try to detect
1666
# them, just getting out if too much redirections occurs. The solution
1667
# is outside: where the loop is defined.
1668
for redirections in range(MAX_REDIRECTIONS):
1670
return action(transport)
1671
except errors.RedirectRequested, e:
1672
redirection_notice = '%s is%s redirected to %s' % (
1673
e.source, e.permanently, e.target)
1674
transport = redirected(transport, e, redirection_notice)
1676
# Loop exited without resolving redirect ? Either the
1677
# user has kept a very very very old reference or a loop
1678
# occurred in the redirections. Nothing we can cure here:
1679
# tell the user. Note that as the user has been informed
1680
# about each redirection (it is the caller responsibility
1681
# to do that in redirected via the provided
1682
# redirection_notice). The caller may provide more
1683
# information if needed (like what file or directory we
1684
# were trying to act upon when the redirection loop
1686
raise errors.TooManyRedirections
1689
824
class Server(object):
1690
825
"""A Transport Server.
1692
The Server interface provides a server for a given transport type.
827
The Server interface provides a server for a given transport. We use
828
these servers as loopback testing tools. For any given transport the
829
Servers it provides must either allow writing, or serve the contents
830
of os.getcwdu() at the time setUp is called.
832
Note that these are real servers - they must implement all the things
833
that we want bzr transports to take advantage of.
1695
def start_server(self):
1696
837
"""Setup the server to service requests."""
1698
def stop_server(self):
1699
840
"""Remove the server and cleanup any resources it owns."""
843
"""Return a url for this server.
845
If the transport does not represent a disk directory (i.e. it is
846
a database like svn, or a memory only transport, it should return
847
a connection to a newly established resource for this Server.
848
Otherwise it should return a url that will provide access to the path
849
that was os.getcwdu() when setUp() was called.
851
Subsequent calls will return the same resource.
853
raise NotImplementedError
855
def get_bogus_url(self):
856
"""Return a url for this protocol, that will fail to connect."""
857
raise NotImplementedError
860
class TransportTestProviderAdapter(object):
861
"""A tool to generate a suite testing all transports for a single test.
863
This is done by copying the test once for each transport and injecting
864
the transport_class and transport_server classes into each copy. Each copy
865
is also given a new id() to make it easy to identify.
868
def adapt(self, test):
870
for klass, server_factory in self._test_permutations():
871
new_test = deepcopy(test)
872
new_test.transport_class = klass
873
new_test.transport_server = server_factory
874
def make_new_test_id():
875
new_id = "%s(%s)" % (new_test.id(), server_factory.__name__)
876
return lambda: new_id
877
new_test.id = make_new_test_id()
878
result.addTest(new_test)
881
def get_transport_test_permutations(self, module):
882
"""Get the permutations module wants to have tested."""
883
if not hasattr(module, 'get_test_permutations'):
884
warning("transport module %s doesn't provide get_test_permutations()"
887
return module.get_test_permutations()
889
def _test_permutations(self):
890
"""Return a list of the klass, server_factory pairs to test."""
892
for module in _get_transport_modules():
894
result.extend(self.get_transport_test_permutations(reduce(getattr,
895
(module).split('.')[1:],
896
__import__(module))))
897
except errors.DependencyNotPresent, e:
898
# Continue even if a dependency prevents us
899
# from running this test
904
class TransportLogger(object):
905
"""Adapt a transport to get clear logging data on api calls.
907
Feel free to extend to log whatever calls are of interest.
910
def __init__(self, adapted):
911
self._adapted = adapted
915
self._calls.append((name,))
916
return self._adapted.get(name)
918
def __getattr__(self, name):
919
"""Thunk all undefined access through to self._adapted."""
920
# raise AttributeError, name
921
return getattr(self._adapted, name)
923
def readv(self, name, offsets):
924
self._calls.append((name, offsets))
925
return self._adapted.readv(name, offsets)
1702
928
# None is the default transport, for things with no url scheme
1703
register_transport_proto('file://',
1704
help="Access using the standard filesystem (default)")
929
register_lazy_transport(None, 'bzrlib.transport.local', 'LocalTransport')
1705
930
register_lazy_transport('file://', 'bzrlib.transport.local', 'LocalTransport')
1706
transport_list_registry.set_default_transport("file://")
1708
register_transport_proto('sftp://',
1709
help="Access using SFTP (most SSH servers provide SFTP).",
1710
register_netloc=True)
1711
931
register_lazy_transport('sftp://', 'bzrlib.transport.sftp', 'SFTPTransport')
1712
# Decorated http transport
1713
register_transport_proto('http+urllib://',
1714
# help="Read-only access of branches exported on the web."
1715
register_netloc=True)
1716
932
register_lazy_transport('http+urllib://', 'bzrlib.transport.http._urllib',
1717
933
'HttpTransport_urllib')
1718
register_transport_proto('https+urllib://',
1719
# help="Read-only access of branches exported on the web using SSL."
1720
register_netloc=True)
1721
934
register_lazy_transport('https+urllib://', 'bzrlib.transport.http._urllib',
1722
935
'HttpTransport_urllib')
1723
register_transport_proto('http+pycurl://',
1724
# help="Read-only access of branches exported on the web."
1725
register_netloc=True)
1726
936
register_lazy_transport('http+pycurl://', 'bzrlib.transport.http._pycurl',
1727
937
'PyCurlTransport')
1728
register_transport_proto('https+pycurl://',
1729
# help="Read-only access of branches exported on the web using SSL."
1730
register_netloc=True)
1731
938
register_lazy_transport('https+pycurl://', 'bzrlib.transport.http._pycurl',
1732
939
'PyCurlTransport')
1733
# Default http transports (last declared wins (if it can be imported))
1734
register_transport_proto('http://',
1735
help="Read-only access of branches exported on the web.")
1736
register_transport_proto('https://',
1737
help="Read-only access of branches exported on the web using SSL.")
1738
# The default http implementation is urllib, but https is pycurl if available
1739
register_lazy_transport('http://', 'bzrlib.transport.http._pycurl',
1741
940
register_lazy_transport('http://', 'bzrlib.transport.http._urllib',
1742
941
'HttpTransport_urllib')
1743
942
register_lazy_transport('https://', 'bzrlib.transport.http._urllib',
1744
943
'HttpTransport_urllib')
1745
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl',
1748
register_transport_proto('ftp://', help="Access using passive FTP.")
944
register_lazy_transport('http://', 'bzrlib.transport.http._pycurl', 'PyCurlTransport')
945
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl', 'PyCurlTransport')
1749
946
register_lazy_transport('ftp://', 'bzrlib.transport.ftp', 'FtpTransport')
1750
register_transport_proto('aftp://', help="Access using active FTP.")
1751
947
register_lazy_transport('aftp://', 'bzrlib.transport.ftp', 'FtpTransport')
1752
register_transport_proto('gio+', help="Access using any GIO supported protocols.")
1753
register_lazy_transport('gio+', 'bzrlib.transport.gio_transport', 'GioTransport')
1756
# Default to trying GSSAPI authentication (if the kerberos module is
1758
register_transport_proto('ftp+gssapi://', register_netloc=True)
1759
register_transport_proto('aftp+gssapi://', register_netloc=True)
1760
register_transport_proto('ftp+nogssapi://', register_netloc=True)
1761
register_transport_proto('aftp+nogssapi://', register_netloc=True)
1762
register_lazy_transport('ftp+gssapi://', 'bzrlib.transport.ftp._gssapi',
1763
'GSSAPIFtpTransport')
1764
register_lazy_transport('aftp+gssapi://', 'bzrlib.transport.ftp._gssapi',
1765
'GSSAPIFtpTransport')
1766
register_lazy_transport('ftp://', 'bzrlib.transport.ftp._gssapi',
1767
'GSSAPIFtpTransport')
1768
register_lazy_transport('aftp://', 'bzrlib.transport.ftp._gssapi',
1769
'GSSAPIFtpTransport')
1770
register_lazy_transport('ftp+nogssapi://', 'bzrlib.transport.ftp',
1772
register_lazy_transport('aftp+nogssapi://', 'bzrlib.transport.ftp',
1775
register_transport_proto('memory://')
1776
register_lazy_transport('memory://', 'bzrlib.transport.memory',
1779
# chroots cannot be implicitly accessed, they must be explicitly created:
1780
register_transport_proto('chroot+')
1782
register_transport_proto('readonly+',
1783
# help="This modifier converts any transport to be readonly."
1785
register_lazy_transport('readonly+', 'bzrlib.transport.readonly',
1786
'ReadonlyTransportDecorator')
1788
register_transport_proto('fakenfs+')
1789
register_lazy_transport('fakenfs+', 'bzrlib.transport.fakenfs',
1790
'FakeNFSTransportDecorator')
1792
register_transport_proto('log+')
1793
register_lazy_transport('log+', 'bzrlib.transport.log', 'TransportLogDecorator')
1795
register_transport_proto('trace+')
1796
register_lazy_transport('trace+', 'bzrlib.transport.trace',
1797
'TransportTraceDecorator')
1799
register_transport_proto('unlistable+')
1800
register_lazy_transport('unlistable+', 'bzrlib.transport.unlistable',
1801
'UnlistableTransportDecorator')
1803
register_transport_proto('brokenrename+')
1804
register_lazy_transport('brokenrename+', 'bzrlib.transport.brokenrename',
1805
'BrokenRenameTransportDecorator')
1807
register_transport_proto('vfat+')
1808
register_lazy_transport('vfat+',
948
register_lazy_transport('memory://', 'bzrlib.transport.memory', 'MemoryTransport')
949
register_lazy_transport('readonly+', 'bzrlib.transport.readonly', 'ReadonlyTransportDecorator')
950
register_lazy_transport('fakenfs+', 'bzrlib.transport.fakenfs', 'FakeNFSTransportDecorator')
951
register_lazy_transport('vfat+',
1809
952
'bzrlib.transport.fakevfat',
1810
953
'FakeVFATTransportDecorator')
1812
register_transport_proto('nosmart+')
1813
register_lazy_transport('nosmart+', 'bzrlib.transport.nosmart',
1814
'NoSmartTransportDecorator')
1816
# These two schemes were registered, but don't seem to have an actual transport
1817
# protocol registered
1818
for scheme in ['ssh', 'bzr+loopback']:
1819
register_urlparse_netloc_protocol(scheme)
1822
register_transport_proto('bzr://',
1823
help="Fast access using the Bazaar smart server.",
1824
register_netloc=True)
1826
register_lazy_transport('bzr://', 'bzrlib.transport.remote',
1827
'RemoteTCPTransport')
1828
register_transport_proto('bzr-v2://', register_netloc=True)
1830
register_lazy_transport('bzr-v2://', 'bzrlib.transport.remote',
1831
'RemoteTCPTransportV2Only')
1832
register_transport_proto('bzr+http://',
1833
# help="Fast access using the Bazaar smart server over HTTP."
1834
register_netloc=True)
1835
register_lazy_transport('bzr+http://', 'bzrlib.transport.remote',
1836
'RemoteHTTPTransport')
1837
register_transport_proto('bzr+https://',
1838
# help="Fast access using the Bazaar smart server over HTTPS."
1839
register_netloc=True)
1840
register_lazy_transport('bzr+https://',
1841
'bzrlib.transport.remote',
1842
'RemoteHTTPTransport')
1843
register_transport_proto('bzr+ssh://',
1844
help="Fast access using the Bazaar smart server over SSH.",
1845
register_netloc=True)
1846
register_lazy_transport('bzr+ssh://', 'bzrlib.transport.remote',
1847
'RemoteSSHTransport')
1849
register_transport_proto('ssh:')
1850
register_lazy_transport('ssh:', 'bzrlib.transport.remote',
1851
'HintingSSHTransport')
1854
transport_server_registry = registry.Registry()
1855
transport_server_registry.register_lazy('bzr', 'bzrlib.smart.server',
1856
'serve_bzr', help="The Bazaar smart server protocol over TCP. (default port: 4155)")
1857
transport_server_registry.default_key = 'bzr'