1
# Copyright (C) 2005 Canonical Ltd
1
# Copyright (C) 2005, 2006 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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""Transport for the local filesystem.
19
This is a fairly thin wrapper on regular file IO."""
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(), """
23
from stat import ST_MODE, S_ISDIR, ST_SIZE
27
38
from bzrlib.trace import mutter
28
from bzrlib.transport import Transport
29
from bzrlib.osutils import abspath, realpath, normpath, pathjoin, rename
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
32
49
class LocalTransport(Transport):
52
75
return LocalTransport(self.base)
54
return LocalTransport(self.abspath(offset))
77
abspath = self.abspath(offset)
78
if abspath == 'file://':
79
# fix upwalk for UNC path
80
# when clone from //HOST/path updir recursively
81
# we should stop at least at //HOST part
83
return LocalTransport(abspath)
85
def _abspath(self, relative_reference):
86
"""Return a path for use in os calls.
88
Several assumptions are made:
89
- relative_reference does not contain '..'
90
- relative_reference is url escaped.
92
if relative_reference in ('.', ''):
93
return self._local_base
94
return self._local_base + urlutils.unescape(relative_reference)
56
96
def abspath(self, relpath):
57
"""Return the full url to the given relative URL.
58
This can be supplied with a string or a list
97
"""Return the full url to the given relative URL."""
98
# TODO: url escape the result. RBC 20060523.
60
99
assert isinstance(relpath, basestring), (type(relpath), relpath)
61
return pathjoin(self.base, urllib.unquote(relpath))
100
# jam 20060426 Using normpath on the real path, because that ensures
101
# proper handling of stuff like
102
path = osutils.normpath(osutils.pathjoin(
103
self._local_base, urlutils.unescape(relpath)))
104
return urlutils.local_path_to_url(path)
106
def local_abspath(self, relpath):
107
"""Transform the given relative path URL into the actual path on disk
109
This function only exists for the LocalTransport, since it is
110
the only one that has direct local access.
111
This is mostly for stuff like WorkingTree which needs to know
112
the local working directory.
114
This function is quite expensive: it calls realpath which resolves
117
absurl = self.abspath(relpath)
118
# mutter(u'relpath %s => base: %s, absurl %s', relpath, self.base, absurl)
119
return urlutils.local_path_from_url(absurl)
63
121
def relpath(self, abspath):
64
122
"""Return the local path portion from a given absolute path.
66
from bzrlib.osutils import relpath
67
124
if abspath is None:
69
return relpath(self.base, abspath)
127
return urlutils.file_relpath(
128
urlutils.strip_trailing_slash(self.base),
129
urlutils.strip_trailing_slash(abspath))
71
131
def has(self, relpath):
72
return os.access(self.abspath(relpath), os.F_OK)
132
return os.access(self._abspath(relpath), os.F_OK)
74
134
def get(self, relpath):
75
135
"""Get the file at the given relative path.
77
137
:param relpath: The relative path to the file
139
canonical_url = self.abspath(relpath)
140
if canonical_url in transport._file_streams:
141
transport._file_streams[canonical_url].flush()
80
path = self.abspath(relpath)
143
path = self._abspath(relpath)
81
144
return open(path, 'rb')
82
145
except (IOError, OSError),e:
83
self._translate_error(e, path)
85
def put(self, relpath, f, mode=None):
86
"""Copy the file-like or string object into the location.
88
:param relpath: Location to put the contents, relative to base.
89
:param f: File-like or string object.
91
from bzrlib.atomicfile import AtomicFile
95
path = self.abspath(relpath)
96
fp = AtomicFile(path, 'wb', new_mode=mode)
97
except (IOError, OSError),e:
98
self._translate_error(e, path)
146
if e.errno == errno.EISDIR:
147
return LateReadError(relpath)
148
self._translate_error(e, path)
150
def put_file(self, relpath, f, mode=None):
151
"""Copy the file-like object into the location.
153
:param relpath: Location to put the contents, relative to base.
154
:param f: File-like object.
155
:param mode: The mode for the newly created file,
156
None means just use the default
161
path = self._abspath(relpath)
162
osutils.check_legal_path(path)
163
fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
164
except (IOError, OSError),e:
165
self._translate_error(e, path)
167
length = self._pump(f, fp)
173
def put_bytes(self, relpath, bytes, mode=None):
174
"""Copy the string into the location.
176
:param relpath: Location to put the contents, relative to base.
182
path = self._abspath(relpath)
183
osutils.check_legal_path(path)
184
fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
185
except (IOError, OSError),e:
186
self._translate_error(e, path)
193
def _put_non_atomic_helper(self, relpath, writer,
195
create_parent_dir=False,
197
"""Common functionality information for the put_*_non_atomic.
199
This tracks all the create_parent_dir stuff.
201
:param relpath: the path we are putting to.
202
:param writer: A function that takes an os level file descriptor
203
and writes whatever data it needs to write there.
204
:param mode: The final file mode.
205
:param create_parent_dir: Should we be creating the parent directory
208
abspath = self._abspath(relpath)
210
# os.open() will automatically use the umask
215
fd = os.open(abspath, _put_non_atomic_flags, local_mode)
216
except (IOError, OSError),e:
217
# We couldn't create the file, maybe we need to create
218
# the parent directory, and try again
219
if (not create_parent_dir
220
or e.errno not in (errno.ENOENT,errno.ENOTDIR)):
221
self._translate_error(e, relpath)
222
parent_dir = os.path.dirname(abspath)
224
self._translate_error(e, relpath)
225
self._mkdir(parent_dir, mode=dir_mode)
226
# We created the parent directory, lets try to open the
229
fd = os.open(abspath, _put_non_atomic_flags, local_mode)
230
except (IOError, OSError), e:
231
self._translate_error(e, relpath)
234
if mode is not None and mode != S_IMODE(st.st_mode):
235
# Because of umask, we may still need to chmod the file.
236
# But in the general case, we won't have to
237
os.chmod(abspath, mode)
242
def put_file_non_atomic(self, relpath, f, mode=None,
243
create_parent_dir=False,
245
"""Copy the file-like object into the target location.
247
This function is not strictly safe to use. It is only meant to
248
be used when you already know that the target does not exist.
249
It is not safe, because it will open and truncate the remote
250
file. So there may be a time when the file has invalid contents.
252
:param relpath: The remote location to put the contents.
253
:param f: File-like object.
254
:param mode: Possible access permissions for new file.
255
None means do not set remote permissions.
256
:param create_parent_dir: If we cannot create the target file because
257
the parent directory does not exist, go ahead and
258
create it, and then try again.
261
self._pump_to_fd(f, fd)
262
self._put_non_atomic_helper(relpath, writer, mode=mode,
263
create_parent_dir=create_parent_dir,
266
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
267
create_parent_dir=False, dir_mode=None):
270
self._put_non_atomic_helper(relpath, writer, mode=mode,
271
create_parent_dir=create_parent_dir,
105
274
def iter_files_recursive(self):
106
275
"""Iter the relative paths of files in the transports sub-tree."""
107
276
queue = list(self.list_dir(u'.'))
109
relpath = urllib.quote(queue.pop(0))
278
relpath = queue.pop(0)
110
279
st = self.stat(relpath)
111
280
if S_ISDIR(st[ST_MODE]):
112
281
for i, basename in enumerate(self.list_dir(relpath)):
286
def _mkdir(self, abspath, mode=None):
287
"""Create a real directory, filtering through mode"""
289
# os.mkdir() will filter through umask
294
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
except (IOError, OSError),e:
300
self._translate_error(e, abspath)
117
302
def mkdir(self, relpath, mode=None):
118
303
"""Create a directory at the given path."""
304
self._mkdir(self._abspath(relpath), mode=mode)
306
def open_write_stream(self, relpath, mode=None):
307
"""See Transport.open_write_stream."""
308
# initialise the file
309
self.put_bytes_non_atomic(relpath, "", mode=mode)
310
handle = open(self._abspath(relpath), 'wb')
311
transport._file_streams[self.abspath(relpath)] = handle
312
return transport.FileFileStream(self, relpath, handle)
314
def _get_append_file(self, relpath, mode=None):
315
"""Call os.open() for the given relpath"""
316
file_abspath = self._abspath(relpath)
318
# os.open() will automatically use the umask
121
path = self.abspath(relpath)
323
return file_abspath, os.open(file_abspath, _append_flags, local_mode)
125
324
except (IOError, OSError),e:
126
self._translate_error(e, path)
128
def append(self, relpath, f):
129
"""Append the text in the file-like object into the final
132
fp = open(self.abspath(relpath), 'ab')
325
self._translate_error(e, relpath)
327
def _check_mode_and_size(self, file_abspath, fd, mode=None):
328
"""Check the mode of the file, and return the current size"""
330
if mode is not None and mode != S_IMODE(st.st_mode):
331
# Because of umask, we may still need to chmod the file.
332
# But in the general case, we won't have to
333
os.chmod(file_abspath, mode)
336
def append_file(self, relpath, f, mode=None):
337
"""Append the text in the file-like object into the final location."""
338
file_abspath, fd = self._get_append_file(relpath, mode=mode)
340
result = self._check_mode_and_size(file_abspath, fd, mode=mode)
341
self._pump_to_fd(f, fd)
346
def append_bytes(self, relpath, bytes, mode=None):
347
"""Append the text in the string into the final location."""
348
file_abspath, fd = self._get_append_file(relpath, mode=mode)
350
result = self._check_mode_and_size(file_abspath, fd, mode=mode)
356
def _pump_to_fd(self, fromfile, to_fd):
357
"""Copy contents of one file to another."""
360
b = fromfile.read(BUFSIZE)
135
365
def copy(self, rel_from, rel_to):
136
366
"""Copy the item at rel_from to the location at rel_to"""
138
path_from = self.abspath(rel_from)
139
path_to = self.abspath(rel_to)
367
path_from = self._abspath(rel_from)
368
path_to = self._abspath(rel_to)
141
370
shutil.copy(path_from, path_to)
142
371
except (IOError, OSError),e:
143
372
# TODO: What about path_to?
144
373
self._translate_error(e, path_from)
375
def rename(self, rel_from, rel_to):
376
path_from = self._abspath(rel_from)
378
# *don't* call bzrlib.osutils.rename, because we want to
379
# detect errors on rename
380
os.rename(path_from, self._abspath(rel_to))
381
except (IOError, OSError),e:
382
# TODO: What about path_to?
383
self._translate_error(e, path_from)
146
385
def move(self, rel_from, rel_to):
147
386
"""Move the item at rel_from to the location at rel_to"""
148
path_from = self.abspath(rel_from)
149
path_to = self.abspath(rel_to)
387
path_from = self._abspath(rel_from)
388
path_to = self._abspath(rel_to)
152
rename(path_from, path_to)
391
# this version will delete the destination if necessary
392
osutils.rename(path_from, path_to)
153
393
except (IOError, OSError),e:
154
394
# TODO: What about path_to?
155
395
self._translate_error(e, path_from)
232
479
:return: A lock object, which should be passed to Transport.unlock()
234
481
from bzrlib.lock import WriteLock
235
return WriteLock(self.abspath(relpath))
238
class ScratchTransport(LocalTransport):
239
"""A transport that works in a temporary dir and cleans up after itself.
482
return WriteLock(self._abspath(relpath))
484
def rmdir(self, relpath):
485
"""See Transport.rmdir."""
488
path = self._abspath(relpath)
490
except (IOError, OSError),e:
491
self._translate_error(e, path)
493
def _can_roundtrip_unix_modebits(self):
494
if sys.platform == 'win32':
501
class EmulatedWin32LocalTransport(LocalTransport):
502
"""Special transport for testing Win32 [UNC] paths on non-windows"""
504
def __init__(self, base):
507
super(LocalTransport, self).__init__(base)
508
self._local_base = urlutils._win32_local_path_from_url(base)
510
def abspath(self, relpath):
511
assert isinstance(relpath, basestring), (type(relpath), relpath)
512
path = osutils.normpath(osutils.pathjoin(
513
self._local_base, urlutils.unescape(relpath)))
514
return urlutils._win32_local_path_to_url(path)
516
def clone(self, offset=None):
517
"""Return a new LocalTransport with root at self.base + offset
518
Because the local filesystem does not require a connection,
519
we can just return a new object.
522
return EmulatedWin32LocalTransport(self.base)
524
abspath = self.abspath(offset)
525
if abspath == 'file://':
526
# fix upwalk for UNC path
527
# when clone from //HOST/path updir recursively
528
# we should stop at least at //HOST part
530
return EmulatedWin32LocalTransport(abspath)
533
class LocalURLServer(Server):
534
"""A pretend server for local transports, using file:// urls.
241
The dir only exists for the lifetime of the Python object.
242
Obviously you should not put anything precious in it.
536
Of course no actual server is required to access the local filesystem, so
537
this just exists to tell the test code how to get to it.
245
def __init__(self, base=None):
247
base = tempfile.mkdtemp()
248
super(ScratchTransport, self).__init__(base)
251
shutil.rmtree(self.base, ignore_errors=True)
252
mutter("%r destroyed" % self)
541
"""Setup the server to service requests.
543
:param decorated_transport: ignored by this implementation.
547
"""See Transport.Server.get_url."""
548
return urlutils.local_path_to_url('')
551
def get_test_permutations():
552
"""Return the permutations to be used in testing."""
554
(LocalTransport, LocalURLServer),