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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
"""Transport for the local filesystem.
19
19
This is a fairly thin wrapper on regular file IO.
23
from stat import ST_MODE, S_ISDIR, ST_SIZE, S_IMODE
26
from bzrlib.lazy_import import lazy_import
27
lazy_import(globals(), """
26
from stat import ST_MODE, S_ISDIR, ST_SIZE, S_IMODE
29
31
from bzrlib import (
34
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename,
35
check_legal_path, rmtree)
36
from bzrlib.symbol_versioning import warn
37
38
from bzrlib.trace import mutter
38
from bzrlib.transport import Transport, Server
41
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY
42
_put_non_atomic_flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | osutils.O_BINARY
45
class LocalTransport(Transport):
39
from bzrlib.transport import LateReadError
42
from bzrlib import transport
45
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY | osutils.O_NOINHERIT
46
_put_non_atomic_flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | osutils.O_BINARY | osutils.O_NOINHERIT
49
class LocalTransport(transport.Transport):
46
50
"""This is the transport agent for local filesystem access."""
48
52
def __init__(self, base):
49
53
"""Set the base path where files will be stored."""
50
54
if not base.startswith('file://'):
51
warn("Instantiating LocalTransport with a filesystem path"
55
symbol_versioning.warn(
56
"Instantiating LocalTransport with a filesystem path"
52
57
" is deprecated as of bzr 0.8."
53
58
" Please use bzrlib.transport.get_transport()"
54
59
" or pass in a file:// url.",
58
63
base = urlutils.local_path_to_url(base)
59
64
if base[-1] != '/':
67
# Special case : windows has no "root", but does have
68
# multiple lettered drives inside it. #240910
69
if sys.platform == 'win32' and base == 'file:///':
72
super(LocalTransport, self).__init__(base)
61
75
super(LocalTransport, self).__init__(base)
62
76
self._local_base = urlutils.local_path_from_url(base)
64
def should_cache(self):
67
78
def clone(self, offset=None):
68
79
"""Return a new LocalTransport with root at self.base + offset
69
Because the local filesystem does not require a connection,
80
Because the local filesystem does not require a connection,
70
81
we can just return a new object.
73
84
return LocalTransport(self.base)
75
return LocalTransport(self.abspath(offset))
86
abspath = self.abspath(offset)
87
if abspath == 'file://':
88
# fix upwalk for UNC path
89
# when clone from //HOST/path updir recursively
90
# we should stop at least at //HOST part
92
return LocalTransport(abspath)
77
94
def _abspath(self, relative_reference):
78
95
"""Return a path for use in os calls.
88
105
def abspath(self, relpath):
89
106
"""Return the full url to the given relative URL."""
90
107
# TODO: url escape the result. RBC 20060523.
91
assert isinstance(relpath, basestring), (type(relpath), relpath)
92
108
# jam 20060426 Using normpath on the real path, because that ensures
93
109
# proper handling of stuff like
94
path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
110
path = osutils.normpath(osutils.pathjoin(
111
self._local_base, urlutils.unescape(relpath)))
112
# on windows, our _local_base may or may not have a drive specified
113
# (ie, it may be "/" or "c:/foo").
114
# If 'relpath' is '/' we *always* get back an abspath without
115
# the drive letter - but if our transport already has a drive letter,
116
# we want our abspaths to have a drive letter too - so handle that
118
if (sys.platform == "win32" and self._local_base[1:2] == ":"
120
path = self._local_base[:3]
95
122
return urlutils.local_path_to_url(path)
97
124
def local_abspath(self, relpath):
100
127
This function only exists for the LocalTransport, since it is
101
128
the only one that has direct local access.
102
129
This is mostly for stuff like WorkingTree which needs to know
103
the local working directory.
130
the local working directory. The returned path will always contain
131
forward slashes as the path separator, regardless of the platform.
105
133
This function is quite expensive: it calls realpath which resolves
128
156
:param relpath: The relative path to the file
158
canonical_url = self.abspath(relpath)
159
if canonical_url in transport._file_streams:
160
transport._file_streams[canonical_url].flush()
131
162
path = self._abspath(relpath)
132
return open(path, 'rb')
163
return osutils.open_file(path, 'rb')
133
164
except (IOError, OSError),e:
165
if e.errno == errno.EISDIR:
166
return LateReadError(relpath)
134
167
self._translate_error(e, path)
136
169
def put_file(self, relpath, f, mode=None):
139
172
:param relpath: Location to put the contents, relative to base.
140
173
:param f: File-like object.
141
:param mode: The mode for the newly created file,
174
:param mode: The mode for the newly created file,
142
175
None means just use the default
147
180
path = self._abspath(relpath)
148
check_legal_path(path)
181
osutils.check_legal_path(path)
149
182
fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
150
183
except (IOError, OSError),e:
151
184
self._translate_error(e, path)
186
length = self._pump(f, fp)
158
192
def put_bytes(self, relpath, bytes, mode=None):
159
193
"""Copy the string into the location.
167
201
path = self._abspath(relpath)
168
check_legal_path(path)
202
osutils.check_legal_path(path)
169
203
fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
170
204
except (IOError, OSError),e:
171
205
self._translate_error(e, path)
251
286
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
252
287
create_parent_dir=False, dir_mode=None):
255
291
self._put_non_atomic_helper(relpath, writer, mode=mode,
256
292
create_parent_dir=create_parent_dir,
257
293
dir_mode=dir_mode)
288
324
"""Create a directory at the given path."""
289
325
self._mkdir(self._abspath(relpath), mode=mode)
327
def open_write_stream(self, relpath, mode=None):
328
"""See Transport.open_write_stream."""
329
# initialise the file
330
self.put_bytes_non_atomic(relpath, "", mode=mode)
331
abspath = self._abspath(relpath)
332
handle = osutils.open_file(abspath, 'wb')
334
self._check_mode_and_size(abspath, handle.fileno(), mode)
335
transport._file_streams[self.abspath(relpath)] = handle
336
return transport.FileFileStream(self, relpath, handle)
291
338
def _get_append_file(self, relpath, mode=None):
292
339
"""Call os.open() for the given relpath"""
293
340
file_abspath = self._abspath(relpath)
352
400
def rename(self, rel_from, rel_to):
353
401
path_from = self._abspath(rel_from)
402
path_to = self._abspath(rel_to)
355
# *don't* call bzrlib.osutils.rename, because we want to
356
# detect errors on rename
357
os.rename(path_from, self._abspath(rel_to))
404
# *don't* call bzrlib.osutils.rename, because we want to
405
# detect conflicting names on rename, and osutils.rename tries to
406
# mask cross-platform differences there; however we do update the
407
# exception to include the filenames
408
os.rename(path_from, path_to)
358
409
except (IOError, OSError),e:
359
410
# TODO: What about path_to?
360
self._translate_error(e, path_from)
411
self._translate_error(
412
osutils._add_rename_error_details(e, path_from, path_to),
362
415
def move(self, rel_from, rel_to):
363
416
"""Move the item at rel_from to the location at rel_to"""
368
421
# this version will delete the destination if necessary
369
rename(path_from, path_to)
422
osutils.rename(path_from, path_to)
370
423
except (IOError, OSError),e:
371
424
# TODO: What about path_to?
372
425
self._translate_error(e, path_from)
380
433
except (IOError, OSError),e:
381
434
self._translate_error(e, path)
436
def external_url(self):
437
"""See bzrlib.transport.Transport.external_url."""
438
# File URL's are externally usable.
383
441
def copy_to(self, relpaths, other, mode=None, pb=None):
384
442
"""Copy a set of entries from self into another Transport.
462
520
except (IOError, OSError),e:
463
521
self._translate_error(e, path)
523
if osutils.host_os_dereferences_symlinks():
524
def readlink(self, relpath):
525
"""See Transport.readlink."""
526
return osutils.readlink(self._abspath(relpath))
528
if osutils.hardlinks_good():
529
def hardlink(self, source, link_name):
530
"""See Transport.link."""
532
os.link(self._abspath(source), self._abspath(link_name))
533
except (IOError, OSError), e:
534
self._translate_error(e, source)
536
if osutils.has_symlinks():
537
def symlink(self, source, link_name):
538
"""See Transport.symlink."""
539
abs_link_dirpath = urlutils.dirname(self.abspath(link_name))
540
source_rel = urlutils.file_relpath(
541
urlutils.strip_trailing_slash(abs_link_dirpath),
542
urlutils.strip_trailing_slash(self.abspath(source))
546
os.symlink(source_rel, self._abspath(link_name))
547
except (IOError, OSError), e:
548
self._translate_error(e, source_rel)
465
550
def _can_roundtrip_unix_modebits(self):
466
551
if sys.platform == 'win32':
473
class LocalRelpathServer(Server):
474
"""A pretend server for local transports, using relpaths."""
477
"""See Transport.Server.get_url."""
481
class LocalAbspathServer(Server):
482
"""A pretend server for local transports, using absolute paths."""
485
"""See Transport.Server.get_url."""
486
return os.path.abspath("")
489
class LocalURLServer(Server):
490
"""A pretend server for local transports, using file:// urls."""
493
"""See Transport.Server.get_url."""
494
return urlutils.local_path_to_url('')
558
class EmulatedWin32LocalTransport(LocalTransport):
559
"""Special transport for testing Win32 [UNC] paths on non-windows"""
561
def __init__(self, base):
564
super(LocalTransport, self).__init__(base)
565
self._local_base = urlutils._win32_local_path_from_url(base)
567
def abspath(self, relpath):
568
path = osutils.normpath(osutils.pathjoin(
569
self._local_base, urlutils.unescape(relpath)))
570
return urlutils._win32_local_path_to_url(path)
572
def clone(self, offset=None):
573
"""Return a new LocalTransport with root at self.base + offset
574
Because the local filesystem does not require a connection,
575
we can just return a new object.
578
return EmulatedWin32LocalTransport(self.base)
580
abspath = self.abspath(offset)
581
if abspath == 'file://':
582
# fix upwalk for UNC path
583
# when clone from //HOST/path updir recursively
584
# we should stop at least at //HOST part
586
return EmulatedWin32LocalTransport(abspath)
497
589
def get_test_permutations():
498
590
"""Return the permutations to be used in testing."""
499
return [(LocalTransport, LocalRelpathServer),
500
(LocalTransport, LocalAbspathServer),
501
(LocalTransport, LocalURLServer),
591
from bzrlib.tests import test_server
592
return [(LocalTransport, test_server.LocalURLServer),]