1
# Copyright (C) 2005, 2006 Canonical Ltd
1
# Copyright (C) 2005-2011 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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.
22
from __future__ import absolute_import
23
from stat import ST_MODE, S_ISDIR, ST_SIZE, S_IMODE
25
from stat import ST_MODE, S_ISDIR, S_IMODE
26
28
from bzrlib.lazy_import import lazy_import
37
from bzrlib.trace import mutter
38
39
from bzrlib.transport import LateReadError
41
from bzrlib.transport import Transport, Server
44
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY
45
_put_non_atomic_flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | osutils.O_BINARY
48
class LocalTransport(Transport):
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):
49
50
"""This is the transport agent for local filesystem access."""
51
52
def __init__(self, base):
52
53
"""Set the base path where files will be stored."""
53
54
if not base.startswith('file://'):
54
symbol_versioning.warn(
55
"Instantiating LocalTransport with a filesystem path"
56
" is deprecated as of bzr 0.8."
57
" Please use bzrlib.transport.get_transport()"
58
" or pass in a file:// url.",
62
base = urlutils.local_path_to_url(base)
55
raise AssertionError("not a file:// url: %r" % base)
63
56
if base[-1] != '/':
59
# Special case : windows has no "root", but does have
60
# multiple lettered drives inside it. #240910
61
if sys.platform == 'win32' and base == 'file:///':
64
super(LocalTransport, self).__init__(base)
65
67
super(LocalTransport, self).__init__(base)
66
68
self._local_base = urlutils.local_path_from_url(base)
68
def should_cache(self):
69
if self._local_base[-1] != '/':
70
self._local_base = self._local_base + '/'
71
72
def clone(self, offset=None):
72
73
"""Return a new LocalTransport with root at self.base + offset
73
Because the local filesystem does not require a connection,
74
Because the local filesystem does not require a connection,
74
75
we can just return a new object.
92
93
- relative_reference is url escaped.
94
95
if relative_reference in ('.', ''):
95
return self._local_base
96
# _local_base normally has a trailing slash; strip it so that stat
97
# on a transport pointing to a symlink reads the link not the
98
# referent but be careful of / and c:\
99
return osutils.split(self._local_base)[0]
96
100
return self._local_base + urlutils.unescape(relative_reference)
98
102
def abspath(self, relpath):
99
103
"""Return the full url to the given relative URL."""
100
104
# TODO: url escape the result. RBC 20060523.
101
assert isinstance(relpath, basestring), (type(relpath), relpath)
102
105
# jam 20060426 Using normpath on the real path, because that ensures
103
106
# proper handling of stuff like
104
107
path = osutils.normpath(osutils.pathjoin(
105
108
self._local_base, urlutils.unescape(relpath)))
109
# on windows, our _local_base may or may not have a drive specified
110
# (ie, it may be "/" or "c:/foo").
111
# If 'relpath' is '/' we *always* get back an abspath without
112
# the drive letter - but if our transport already has a drive letter,
113
# we want our abspaths to have a drive letter too - so handle that
115
if (sys.platform == "win32" and self._local_base[1:2] == ":"
117
path = self._local_base[:3]
106
119
return urlutils.local_path_to_url(path)
108
121
def local_abspath(self, relpath):
111
124
This function only exists for the LocalTransport, since it is
112
125
the only one that has direct local access.
113
126
This is mostly for stuff like WorkingTree which needs to know
114
the local working directory.
127
the local working directory. The returned path will always contain
128
forward slashes as the path separator, regardless of the platform.
116
130
This function is quite expensive: it calls realpath which resolves
126
140
if abspath is None:
129
return urlutils.file_relpath(
130
urlutils.strip_trailing_slash(self.base),
131
urlutils.strip_trailing_slash(abspath))
143
return urlutils.file_relpath(self.base, abspath)
133
145
def has(self, relpath):
134
146
return os.access(self._abspath(relpath), os.F_OK)
139
151
:param relpath: The relative path to the file
153
canonical_url = self.abspath(relpath)
154
if canonical_url in transport._file_streams:
155
transport._file_streams[canonical_url].flush()
142
157
path = self._abspath(relpath)
143
return open(path, 'rb')
158
return osutils.open_file(path, 'rb')
144
159
except (IOError, OSError),e:
145
160
if e.errno == errno.EISDIR:
146
161
return LateReadError(relpath)
152
167
:param relpath: Location to put the contents, relative to base.
153
168
:param f: File-like object.
154
:param mode: The mode for the newly created file,
169
:param mode: The mode for the newly created file,
155
170
None means just use the default
163
178
except (IOError, OSError),e:
164
179
self._translate_error(e, path)
181
length = self._pump(f, fp)
171
187
def put_bytes(self, relpath, bytes, mode=None):
172
188
"""Copy the string into the location.
232
249
if mode is not None and mode != S_IMODE(st.st_mode):
233
250
# Because of umask, we may still need to chmod the file.
234
251
# But in the general case, we won't have to
235
os.chmod(abspath, mode)
252
osutils.chmod_if_possible(abspath, mode)
264
281
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
265
282
create_parent_dir=False, dir_mode=None):
268
286
self._put_non_atomic_helper(relpath, writer, mode=mode,
269
287
create_parent_dir=create_parent_dir,
270
288
dir_mode=dir_mode)
290
308
local_mode = mode
292
310
os.mkdir(abspath, local_mode)
294
# It is probably faster to just do the chmod, rather than
295
# doing a stat, and then trying to compare
296
os.chmod(abspath, mode)
297
311
except (IOError, OSError),e:
298
312
self._translate_error(e, abspath)
315
osutils.chmod_if_possible(abspath, mode)
316
except (IOError, OSError), e:
317
self._translate_error(e, abspath)
300
319
def mkdir(self, relpath, mode=None):
301
320
"""Create a directory at the given path."""
302
321
self._mkdir(self._abspath(relpath), mode=mode)
323
def open_write_stream(self, relpath, mode=None):
324
"""See Transport.open_write_stream."""
325
abspath = self._abspath(relpath)
327
handle = osutils.open_file(abspath, 'wb')
328
except (IOError, OSError),e:
329
self._translate_error(e, abspath)
332
self._check_mode_and_size(abspath, handle.fileno(), mode)
333
transport._file_streams[self.abspath(relpath)] = handle
334
return transport.FileFileStream(self, relpath, handle)
304
336
def _get_append_file(self, relpath, mode=None):
305
337
"""Call os.open() for the given relpath"""
306
338
file_abspath = self._abspath(relpath)
320
352
if mode is not None and mode != S_IMODE(st.st_mode):
321
353
# Because of umask, we may still need to chmod the file.
322
354
# But in the general case, we won't have to
323
os.chmod(file_abspath, mode)
355
osutils.chmod_if_possible(file_abspath, mode)
324
356
return st.st_size
326
358
def append_file(self, relpath, f, mode=None):
365
398
def rename(self, rel_from, rel_to):
366
399
path_from = self._abspath(rel_from)
400
path_to = self._abspath(rel_to)
368
# *don't* call bzrlib.osutils.rename, because we want to
369
# detect errors on rename
370
os.rename(path_from, self._abspath(rel_to))
402
# *don't* call bzrlib.osutils.rename, because we want to
403
# detect conflicting names on rename, and osutils.rename tries to
404
# mask cross-platform differences there
405
os.rename(path_from, path_to)
371
406
except (IOError, OSError),e:
372
407
# TODO: What about path_to?
373
408
self._translate_error(e, path_from)
416
451
otherpath = other._abspath(path)
417
452
shutil.copy(mypath, otherpath)
418
453
if mode is not None:
419
os.chmod(otherpath, mode)
454
osutils.chmod_if_possible(otherpath, mode)
420
455
except (IOError, OSError),e:
421
456
self._translate_error(e, path)
480
515
except (IOError, OSError),e:
481
516
self._translate_error(e, path)
518
if osutils.host_os_dereferences_symlinks():
519
def readlink(self, relpath):
520
"""See Transport.readlink."""
521
return osutils.readlink(self._abspath(relpath))
523
if osutils.hardlinks_good():
524
def hardlink(self, source, link_name):
525
"""See Transport.link."""
527
os.link(self._abspath(source), self._abspath(link_name))
528
except (IOError, OSError), e:
529
self._translate_error(e, source)
531
if osutils.has_symlinks():
532
def symlink(self, source, link_name):
533
"""See Transport.symlink."""
534
abs_link_dirpath = urlutils.dirname(self.abspath(link_name))
535
source_rel = urlutils.file_relpath(
536
abs_link_dirpath, self.abspath(source))
539
os.symlink(source_rel, self._abspath(link_name))
540
except (IOError, OSError), e:
541
self._translate_error(e, source_rel)
483
543
def _can_roundtrip_unix_modebits(self):
484
544
if sys.platform == 'win32':
498
558
self._local_base = urlutils._win32_local_path_from_url(base)
500
560
def abspath(self, relpath):
501
assert isinstance(relpath, basestring), (type(relpath), relpath)
502
path = osutils.normpath(osutils.pathjoin(
561
path = osutils._win32_normpath(osutils.pathjoin(
503
562
self._local_base, urlutils.unescape(relpath)))
504
563
return urlutils._win32_local_path_to_url(path)
506
565
def clone(self, offset=None):
507
566
"""Return a new LocalTransport with root at self.base + offset
508
Because the local filesystem does not require a connection,
567
Because the local filesystem does not require a connection,
509
568
we can just return a new object.
511
570
if offset is None:
520
579
return EmulatedWin32LocalTransport(abspath)
523
class LocalURLServer(Server):
524
"""A pretend server for local transports, using file:// urls.
526
Of course no actual server is required to access the local filesystem, so
527
this just exists to tell the test code how to get to it.
531
"""Setup the server to service requests.
533
:param decorated_transport: ignored by this implementation.
537
"""See Transport.Server.get_url."""
538
return urlutils.local_path_to_url('')
541
582
def get_test_permutations():
542
583
"""Return the permutations to be used in testing."""
544
(LocalTransport, LocalURLServer),
584
from bzrlib.tests import test_server
585
return [(LocalTransport, test_server.LocalURLServer),]