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):
63
63
base = urlutils.local_path_to_url(base)
64
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)
66
75
super(LocalTransport, self).__init__(base)
67
76
self._local_base = urlutils.local_path_from_url(base)
77
if self._local_base[-1] != '/':
78
self._local_base = self._local_base + '/'
69
80
def clone(self, offset=None):
70
81
"""Return a new LocalTransport with root at self.base + offset
71
Because the local filesystem does not require a connection,
82
Because the local filesystem does not require a connection,
72
83
we can just return a new object.
90
101
- relative_reference is url escaped.
92
103
if relative_reference in ('.', ''):
93
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]
94
108
return self._local_base + urlutils.unescape(relative_reference)
96
110
def abspath(self, relpath):
97
111
"""Return the full url to the given relative URL."""
98
112
# TODO: url escape the result. RBC 20060523.
99
assert isinstance(relpath, basestring), (type(relpath), relpath)
100
113
# jam 20060426 Using normpath on the real path, because that ensures
101
114
# proper handling of stuff like
102
115
path = osutils.normpath(osutils.pathjoin(
103
116
self._local_base, urlutils.unescape(relpath)))
117
# on windows, our _local_base may or may not have a drive specified
118
# (ie, it may be "/" or "c:/foo").
119
# If 'relpath' is '/' we *always* get back an abspath without
120
# the drive letter - but if our transport already has a drive letter,
121
# we want our abspaths to have a drive letter too - so handle that
123
if (sys.platform == "win32" and self._local_base[1:2] == ":"
125
path = self._local_base[:3]
104
127
return urlutils.local_path_to_url(path)
106
129
def local_abspath(self, relpath):
109
132
This function only exists for the LocalTransport, since it is
110
133
the only one that has direct local access.
111
134
This is mostly for stuff like WorkingTree which needs to know
112
the local working directory.
135
the local working directory. The returned path will always contain
136
forward slashes as the path separator, regardless of the platform.
114
138
This function is quite expensive: it calls realpath which resolves
124
148
if abspath is None:
127
return urlutils.file_relpath(
128
urlutils.strip_trailing_slash(self.base),
129
urlutils.strip_trailing_slash(abspath))
151
return urlutils.file_relpath(self.base, abspath)
131
153
def has(self, relpath):
132
154
return os.access(self._abspath(relpath), os.F_OK)
141
163
transport._file_streams[canonical_url].flush()
143
165
path = self._abspath(relpath)
144
return open(path, 'rb')
166
return osutils.open_file(path, 'rb')
145
167
except (IOError, OSError),e:
146
168
if e.errno == errno.EISDIR:
147
169
return LateReadError(relpath)
153
175
:param relpath: Location to put the contents, relative to base.
154
176
:param f: File-like object.
155
:param mode: The mode for the newly created file,
177
:param mode: The mode for the newly created file,
156
178
None means just use the default
234
257
if mode is not None and mode != S_IMODE(st.st_mode):
235
258
# Because of umask, we may still need to chmod the file.
236
259
# But in the general case, we won't have to
237
os.chmod(abspath, mode)
260
osutils.chmod_if_possible(abspath, mode)
266
289
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
267
290
create_parent_dir=False, dir_mode=None):
270
294
self._put_non_atomic_helper(relpath, writer, mode=mode,
271
295
create_parent_dir=create_parent_dir,
272
296
dir_mode=dir_mode)
292
316
local_mode = mode
294
318
os.mkdir(abspath, local_mode)
296
# It is probably faster to just do the chmod, rather than
297
# doing a stat, and then trying to compare
298
os.chmod(abspath, mode)
299
319
except (IOError, OSError),e:
300
320
self._translate_error(e, abspath)
323
osutils.chmod_if_possible(abspath, mode)
324
except (IOError, OSError), e:
325
self._translate_error(e, abspath)
302
327
def mkdir(self, relpath, mode=None):
303
328
"""Create a directory at the given path."""
306
331
def open_write_stream(self, relpath, mode=None):
307
332
"""See Transport.open_write_stream."""
308
# initialise the file
309
self.put_bytes_non_atomic(relpath, "", mode=mode)
310
333
abspath = self._abspath(relpath)
311
handle = open(abspath, 'wb')
335
handle = osutils.open_file(abspath, 'wb')
336
except (IOError, OSError),e:
337
self._translate_error(e, abspath)
312
339
if mode is not None:
313
340
self._check_mode_and_size(abspath, handle.fileno(), mode)
314
341
transport._file_streams[self.abspath(relpath)] = handle
333
360
if mode is not None and mode != S_IMODE(st.st_mode):
334
361
# Because of umask, we may still need to chmod the file.
335
362
# But in the general case, we won't have to
336
os.chmod(file_abspath, mode)
363
osutils.chmod_if_possible(file_abspath, mode)
337
364
return st.st_size
339
366
def append_file(self, relpath, f, mode=None):
378
406
def rename(self, rel_from, rel_to):
379
407
path_from = self._abspath(rel_from)
408
path_to = self._abspath(rel_to)
381
# *don't* call bzrlib.osutils.rename, because we want to
382
# detect errors on rename
383
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)
384
414
except (IOError, OSError),e:
385
415
# TODO: What about path_to?
386
416
self._translate_error(e, path_from)
429
459
otherpath = other._abspath(path)
430
460
shutil.copy(mypath, otherpath)
431
461
if mode is not None:
432
os.chmod(otherpath, mode)
462
osutils.chmod_if_possible(otherpath, mode)
433
463
except (IOError, OSError),e:
434
464
self._translate_error(e, path)
493
523
except (IOError, OSError),e:
494
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)
496
551
def _can_roundtrip_unix_modebits(self):
497
552
if sys.platform == 'win32':
511
566
self._local_base = urlutils._win32_local_path_from_url(base)
513
568
def abspath(self, relpath):
514
assert isinstance(relpath, basestring), (type(relpath), relpath)
515
path = osutils.normpath(osutils.pathjoin(
569
path = osutils._win32_normpath(osutils.pathjoin(
516
570
self._local_base, urlutils.unescape(relpath)))
517
571
return urlutils._win32_local_path_to_url(path)
519
573
def clone(self, offset=None):
520
574
"""Return a new LocalTransport with root at self.base + offset
521
Because the local filesystem does not require a connection,
575
Because the local filesystem does not require a connection,
522
576
we can just return a new object.
524
578
if offset is None:
533
587
return EmulatedWin32LocalTransport(abspath)
536
class LocalURLServer(Server):
537
"""A pretend server for local transports, using file:// urls.
539
Of course no actual server is required to access the local filesystem, so
540
this just exists to tell the test code how to get to it.
544
"""Setup the server to service requests.
546
:param decorated_transport: ignored by this implementation.
550
"""See Transport.Server.get_url."""
551
return urlutils.local_path_to_url('')
554
590
def get_test_permutations():
555
591
"""Return the permutations to be used in testing."""
557
(LocalTransport, LocalURLServer),
592
from bzrlib.tests import test_server
593
return [(LocalTransport, test_server.LocalURLServer),]