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
38
from bzrlib.trace import mutter
39
39
from bzrlib.transport import LateReadError
42
from bzrlib.transport import Transport, Server
45
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY
46
_put_non_atomic_flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | osutils.O_BINARY
49
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):
50
50
"""This is the transport agent for local filesystem access."""
52
52
def __init__(self, base):
71
71
self._local_base = ''
72
72
super(LocalTransport, self).__init__(base)
75
75
super(LocalTransport, self).__init__(base)
76
76
self._local_base = urlutils.local_path_from_url(base)
77
if self._local_base[-1] != '/':
78
self._local_base = self._local_base + '/'
78
80
def clone(self, offset=None):
79
81
"""Return a new LocalTransport with root at self.base + offset
80
Because the local filesystem does not require a connection,
82
Because the local filesystem does not require a connection,
81
83
we can just return a new object.
99
101
- relative_reference is url escaped.
101
103
if relative_reference in ('.', ''):
102
return self._local_base
104
# _local_base normally has a trailing slash; strip it so that stat
105
# on a transport pointing to a symlink reads the link not the
106
# referent but be careful of / and c:\
107
return osutils.split(self._local_base)[0]
103
108
return self._local_base + urlutils.unescape(relative_reference)
105
110
def abspath(self, relpath):
143
148
if abspath is None:
146
return urlutils.file_relpath(
147
urlutils.strip_trailing_slash(self.base),
148
urlutils.strip_trailing_slash(abspath))
151
return urlutils.file_relpath(self.base, abspath)
150
153
def has(self, relpath):
151
154
return os.access(self._abspath(relpath), os.F_OK)
160
163
transport._file_streams[canonical_url].flush()
162
165
path = self._abspath(relpath)
163
return open(path, 'rb')
166
return osutils.open_file(path, 'rb')
164
167
except (IOError, OSError),e:
165
168
if e.errno == errno.EISDIR:
166
169
return LateReadError(relpath)
172
175
:param relpath: Location to put the contents, relative to base.
173
176
:param f: File-like object.
174
:param mode: The mode for the newly created file,
177
:param mode: The mode for the newly created file,
175
178
None means just use the default
253
257
if mode is not None and mode != S_IMODE(st.st_mode):
254
258
# Because of umask, we may still need to chmod the file.
255
259
# But in the general case, we won't have to
256
os.chmod(abspath, mode)
260
osutils.chmod_if_possible(abspath, mode)
285
289
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
286
290
create_parent_dir=False, dir_mode=None):
289
294
self._put_non_atomic_helper(relpath, writer, mode=mode,
290
295
create_parent_dir=create_parent_dir,
291
296
dir_mode=dir_mode)
311
316
local_mode = mode
313
318
os.mkdir(abspath, local_mode)
315
# It is probably faster to just do the chmod, rather than
316
# doing a stat, and then trying to compare
317
os.chmod(abspath, mode)
318
319
except (IOError, OSError),e:
319
320
self._translate_error(e, abspath)
323
osutils.chmod_if_possible(abspath, mode)
324
except (IOError, OSError), e:
325
self._translate_error(e, abspath)
321
327
def mkdir(self, relpath, mode=None):
322
328
"""Create a directory at the given path."""
325
331
def open_write_stream(self, relpath, mode=None):
326
332
"""See Transport.open_write_stream."""
327
# initialise the file
328
self.put_bytes_non_atomic(relpath, "", mode=mode)
329
333
abspath = self._abspath(relpath)
330
handle = open(abspath, 'wb')
335
handle = osutils.open_file(abspath, 'wb')
336
except (IOError, OSError),e:
337
self._translate_error(e, abspath)
331
339
if mode is not None:
332
340
self._check_mode_and_size(abspath, handle.fileno(), mode)
333
341
transport._file_streams[self.abspath(relpath)] = handle
352
360
if mode is not None and mode != S_IMODE(st.st_mode):
353
361
# Because of umask, we may still need to chmod the file.
354
362
# But in the general case, we won't have to
355
os.chmod(file_abspath, mode)
363
osutils.chmod_if_possible(file_abspath, mode)
356
364
return st.st_size
358
366
def append_file(self, relpath, f, mode=None):
397
406
def rename(self, rel_from, rel_to):
398
407
path_from = self._abspath(rel_from)
408
path_to = self._abspath(rel_to)
400
# *don't* call bzrlib.osutils.rename, because we want to
401
# detect errors on rename
402
os.rename(path_from, self._abspath(rel_to))
410
# *don't* call bzrlib.osutils.rename, because we want to
411
# detect conflicting names on rename, and osutils.rename tries to
412
# mask cross-platform differences there
413
os.rename(path_from, path_to)
403
414
except (IOError, OSError),e:
404
415
# TODO: What about path_to?
405
416
self._translate_error(e, path_from)
448
459
otherpath = other._abspath(path)
449
460
shutil.copy(mypath, otherpath)
450
461
if mode is not None:
451
os.chmod(otherpath, mode)
462
osutils.chmod_if_possible(otherpath, mode)
452
463
except (IOError, OSError),e:
453
464
self._translate_error(e, path)
512
523
except (IOError, OSError),e:
513
524
self._translate_error(e, path)
526
if osutils.host_os_dereferences_symlinks():
527
def readlink(self, relpath):
528
"""See Transport.readlink."""
529
return osutils.readlink(self._abspath(relpath))
531
if osutils.hardlinks_good():
532
def hardlink(self, source, link_name):
533
"""See Transport.link."""
535
os.link(self._abspath(source), self._abspath(link_name))
536
except (IOError, OSError), e:
537
self._translate_error(e, source)
539
if osutils.has_symlinks():
540
def symlink(self, source, link_name):
541
"""See Transport.symlink."""
542
abs_link_dirpath = urlutils.dirname(self.abspath(link_name))
543
source_rel = urlutils.file_relpath(
544
abs_link_dirpath, self.abspath(source))
547
os.symlink(source_rel, self._abspath(link_name))
548
except (IOError, OSError), e:
549
self._translate_error(e, source_rel)
515
551
def _can_roundtrip_unix_modebits(self):
516
552
if sys.platform == 'win32':
530
566
self._local_base = urlutils._win32_local_path_from_url(base)
532
568
def abspath(self, relpath):
533
path = osutils.normpath(osutils.pathjoin(
569
path = osutils._win32_normpath(osutils.pathjoin(
534
570
self._local_base, urlutils.unescape(relpath)))
535
571
return urlutils._win32_local_path_to_url(path)
537
573
def clone(self, offset=None):
538
574
"""Return a new LocalTransport with root at self.base + offset
539
Because the local filesystem does not require a connection,
575
Because the local filesystem does not require a connection,
540
576
we can just return a new object.
542
578
if offset is None:
551
587
return EmulatedWin32LocalTransport(abspath)
554
class LocalURLServer(Server):
555
"""A pretend server for local transports, using file:// urls.
557
Of course no actual server is required to access the local filesystem, so
558
this just exists to tell the test code how to get to it.
562
"""Setup the server to service requests.
564
:param decorated_transport: ignored by this implementation.
568
"""See Transport.Server.get_url."""
569
return urlutils.local_path_to_url('')
572
590
def get_test_permutations():
573
591
"""Return the permutations to be used in testing."""
575
(LocalTransport, LocalURLServer),
592
from bzrlib.tests import test_server
593
return [(LocalTransport, test_server.LocalURLServer),]