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
from bzrlib.lazy_import import lazy_import
28
from ..lazy_import import lazy_import
27
29
lazy_import(globals(), """
38
from bzrlib.trace import mutter
39
from bzrlib.transport import LateReadError
38
from breezy.transport import LateReadError
42
from bzrlib import transport
41
from .. import transport
45
44
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY | osutils.O_NOINHERIT
52
51
def __init__(self, base):
53
52
"""Set the base path where files will be stored."""
54
53
if not base.startswith('file://'):
55
symbol_versioning.warn(
56
"Instantiating LocalTransport with a filesystem path"
57
" is deprecated as of bzr 0.8."
58
" Please use bzrlib.transport.get_transport()"
59
" or pass in a file:// url.",
63
base = urlutils.local_path_to_url(base)
54
raise AssertionError("not a file:// url: %r" % base)
64
55
if base[-1] != '/':
75
66
super(LocalTransport, self).__init__(base)
76
67
self._local_base = urlutils.local_path_from_url(base)
68
if self._local_base[-1] != '/':
69
self._local_base = self._local_base + '/'
78
71
def clone(self, offset=None):
79
72
"""Return a new LocalTransport with root at self.base + offset
99
92
- relative_reference is url escaped.
101
94
if relative_reference in ('.', ''):
102
return self._local_base
95
# _local_base normally has a trailing slash; strip it so that stat
96
# on a transport pointing to a symlink reads the link not the
97
# referent but be careful of / and c:\
98
return osutils.split(self._local_base)[0]
103
99
return self._local_base + urlutils.unescape(relative_reference)
105
101
def abspath(self, relpath):
108
104
# jam 20060426 Using normpath on the real path, because that ensures
109
105
# proper handling of stuff like
110
106
path = osutils.normpath(osutils.pathjoin(
111
self._local_base, urlutils.unescape(relpath)))
107
self._local_base, urlutils.unescape(relpath)))
112
108
# on windows, our _local_base may or may not have a drive specified
113
109
# (ie, it may be "/" or "c:/foo").
114
110
# If 'relpath' is '/' we *always* get back an abspath without
143
139
if abspath is None:
146
return urlutils.file_relpath(
147
urlutils.strip_trailing_slash(self.base),
148
urlutils.strip_trailing_slash(abspath))
142
return urlutils.file_relpath(self.base, abspath)
150
144
def has(self, relpath):
151
145
return os.access(self._abspath(relpath), os.F_OK)
162
156
path = self._abspath(relpath)
163
157
return osutils.open_file(path, 'rb')
164
except (IOError, OSError),e:
158
except (IOError, OSError) as e:
165
159
if e.errno == errno.EISDIR:
166
160
return LateReadError(relpath)
167
161
self._translate_error(e, path)
180
174
path = self._abspath(relpath)
181
175
osutils.check_legal_path(path)
182
176
fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
183
except (IOError, OSError),e:
177
except (IOError, OSError) as e:
184
178
self._translate_error(e, path)
186
180
length = self._pump(f, fp)
192
def put_bytes(self, relpath, bytes, mode=None):
186
def put_bytes(self, relpath, raw_bytes, mode=None):
193
187
"""Copy the string into the location.
195
189
:param relpath: Location to put the contents, relative to base.
190
:param raw_bytes: String
192
if not isinstance(raw_bytes, bytes):
194
'raw_bytes must be bytes, not %s' % type(raw_bytes))
201
197
path = self._abspath(relpath)
202
198
osutils.check_legal_path(path)
203
199
fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
204
except (IOError, OSError),e:
200
except (IOError, OSError) as e:
205
201
self._translate_error(e, path)
228
224
abspath = self._abspath(relpath)
230
226
# os.open() will automatically use the umask
233
229
local_mode = mode
235
231
fd = os.open(abspath, _put_non_atomic_flags, local_mode)
236
except (IOError, OSError),e:
232
except (IOError, OSError) as e:
237
233
# We couldn't create the file, maybe we need to create
238
234
# the parent directory, and try again
239
235
if (not create_parent_dir
240
or e.errno not in (errno.ENOENT,errno.ENOTDIR)):
236
or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
241
237
self._translate_error(e, relpath)
242
238
parent_dir = os.path.dirname(abspath)
243
239
if not parent_dir:
249
245
fd = os.open(abspath, _put_non_atomic_flags, local_mode)
250
except (IOError, OSError), e:
246
except (IOError, OSError) as e:
251
247
self._translate_error(e, relpath)
253
249
st = os.fstat(fd)
254
250
if mode is not None and mode != S_IMODE(st.st_mode):
255
251
# Because of umask, we may still need to chmod the file.
256
252
# But in the general case, we won't have to
257
os.chmod(abspath, mode)
253
osutils.chmod_if_possible(abspath, mode)
308
304
"""Create a real directory, filtering through mode"""
310
306
# os.mkdir() will filter through umask
313
309
local_mode = mode
315
311
os.mkdir(abspath, local_mode)
317
# It is probably faster to just do the chmod, rather than
318
# doing a stat, and then trying to compare
319
os.chmod(abspath, mode)
320
except (IOError, OSError),e:
312
except (IOError, OSError) as e:
321
313
self._translate_error(e, abspath)
316
osutils.chmod_if_possible(abspath, mode)
317
except (IOError, OSError) as e:
318
self._translate_error(e, abspath)
323
320
def mkdir(self, relpath, mode=None):
324
321
"""Create a directory at the given path."""
327
324
def open_write_stream(self, relpath, mode=None):
328
325
"""See Transport.open_write_stream."""
329
# initialise the file
330
self.put_bytes_non_atomic(relpath, "", mode=mode)
331
326
abspath = self._abspath(relpath)
332
handle = osutils.open_file(abspath, 'wb')
328
handle = osutils.open_file(abspath, 'wb')
329
except (IOError, OSError) as e:
330
self._translate_error(e, abspath)
333
332
if mode is not None:
334
333
self._check_mode_and_size(abspath, handle.fileno(), mode)
335
334
transport._file_streams[self.abspath(relpath)] = handle
340
339
file_abspath = self._abspath(relpath)
342
341
# os.open() will automatically use the umask
345
344
local_mode = mode
347
346
return file_abspath, os.open(file_abspath, _append_flags, local_mode)
348
except (IOError, OSError),e:
347
except (IOError, OSError) as e:
349
348
self._translate_error(e, relpath)
351
350
def _check_mode_and_size(self, file_abspath, fd, mode=None):
354
353
if mode is not None and mode != S_IMODE(st.st_mode):
355
354
# Because of umask, we may still need to chmod the file.
356
355
# But in the general case, we won't have to
357
os.chmod(file_abspath, mode)
356
osutils.chmod_if_possible(file_abspath, mode)
358
357
return st.st_size
360
359
def append_file(self, relpath, f, mode=None):
393
392
path_to = self._abspath(rel_to)
395
394
shutil.copy(path_from, path_to)
396
except (IOError, OSError),e:
395
except (IOError, OSError) as e:
397
396
# TODO: What about path_to?
398
397
self._translate_error(e, path_from)
401
400
path_from = self._abspath(rel_from)
402
401
path_to = self._abspath(rel_to)
404
# *don't* call bzrlib.osutils.rename, because we want to
403
# *don't* call breezy.osutils.rename, because we want to
405
404
# 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
405
# mask cross-platform differences there
408
406
os.rename(path_from, path_to)
409
except (IOError, OSError),e:
407
except (IOError, OSError) as e:
410
408
# TODO: What about path_to?
411
self._translate_error(
412
osutils._add_rename_error_details(e, path_from, path_to),
409
self._translate_error(e, path_from)
415
411
def move(self, rel_from, rel_to):
416
412
"""Move the item at rel_from to the location at rel_to"""
421
417
# this version will delete the destination if necessary
422
418
osutils.rename(path_from, path_to)
423
except (IOError, OSError),e:
419
except (IOError, OSError) as e:
424
420
# TODO: What about path_to?
425
421
self._translate_error(e, path_from)
431
427
path = self._abspath(relpath)
433
except (IOError, OSError),e:
429
except (IOError, OSError) as e:
434
430
self._translate_error(e, path)
436
432
def external_url(self):
437
"""See bzrlib.transport.Transport.external_url."""
433
"""See breezy.transport.Transport.external_url."""
438
434
# File URL's are externally usable.
456
452
otherpath = other._abspath(path)
457
453
shutil.copy(mypath, otherpath)
458
454
if mode is not None:
459
os.chmod(otherpath, mode)
460
except (IOError, OSError),e:
455
osutils.chmod_if_possible(otherpath, mode)
456
except (IOError, OSError) as e:
461
457
self._translate_error(e, path)
476
472
path = self._abspath(relpath)
478
474
entries = os.listdir(path)
479
except (IOError, OSError), e:
475
except (IOError, OSError) as e:
480
476
self._translate_error(e, path)
481
477
return [urlutils.escape(entry) for entry in entries]
488
484
path = self._abspath(relpath)
489
485
return os.lstat(path)
490
except (IOError, OSError),e:
486
except (IOError, OSError) as e:
491
487
self._translate_error(e, path)
493
489
def lock_read(self, relpath):
494
490
"""Lock the given file for shared (read) access.
495
491
:return: A lock object, which should be passed to Transport.unlock()
497
from bzrlib.lock import ReadLock
493
from breezy.lock import ReadLock
500
496
path = self._abspath(relpath)
501
497
return ReadLock(path)
502
except (IOError, OSError), e:
498
except (IOError, OSError) as e:
503
499
self._translate_error(e, path)
505
501
def lock_write(self, relpath):
509
505
:return: A lock object, which should be passed to Transport.unlock()
511
from bzrlib.lock import WriteLock
507
from breezy.lock import WriteLock
512
508
return WriteLock(self._abspath(relpath))
514
510
def rmdir(self, relpath):
518
514
path = self._abspath(relpath)
520
except (IOError, OSError),e:
516
except (IOError, OSError) as e:
521
517
self._translate_error(e, path)
523
519
if osutils.host_os_dereferences_symlinks():
524
520
def readlink(self, relpath):
525
521
"""See Transport.readlink."""
526
return osutils.readlink(self._abspath(relpath))
523
return osutils.readlink(self._abspath(relpath))
524
except (IOError, OSError) as e:
525
self._translate_error(e, relpath)
528
527
if osutils.hardlinks_good():
529
528
def hardlink(self, source, link_name):
530
529
"""See Transport.link."""
532
531
os.link(self._abspath(source), self._abspath(link_name))
533
except (IOError, OSError), e:
532
except (IOError, OSError) as e:
534
533
self._translate_error(e, source)
536
if osutils.has_symlinks():
535
if getattr(os, 'symlink', None) is not None:
537
536
def symlink(self, source, link_name):
538
537
"""See Transport.symlink."""
539
538
abs_link_dirpath = urlutils.dirname(self.abspath(link_name))
540
539
source_rel = urlutils.file_relpath(
541
urlutils.strip_trailing_slash(abs_link_dirpath),
542
urlutils.strip_trailing_slash(self.abspath(source))
540
abs_link_dirpath, self.abspath(source))
546
543
os.symlink(source_rel, self._abspath(link_name))
547
except (IOError, OSError), e:
544
except (IOError, OSError) as e:
548
545
self._translate_error(e, source_rel)
550
547
def _can_roundtrip_unix_modebits(self):
565
562
self._local_base = urlutils._win32_local_path_from_url(base)
567
564
def abspath(self, relpath):
568
path = osutils.normpath(osutils.pathjoin(
569
self._local_base, urlutils.unescape(relpath)))
565
path = osutils._win32_normpath(osutils.pathjoin(
566
self._local_base, urlutils.unescape(relpath)))
570
567
return urlutils._win32_local_path_to_url(path)
572
569
def clone(self, offset=None):
589
586
def get_test_permutations():
590
587
"""Return the permutations to be used in testing."""
591
from bzrlib.tests import test_server
592
return [(LocalTransport, test_server.LocalURLServer),]
588
from ..tests import test_server
589
return [(LocalTransport, test_server.LocalURLServer), ]