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
"""Transport for the local filesystem.
19
This is a fairly thin wrapper on regular file IO.
16
"""Implementation of Transport for the local filesystem.
25
from stat import ST_MODE, S_ISDIR, ST_SIZE
28
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename,
29
check_legal_path, rmtree)
30
from bzrlib.symbol_versioning import warn
31
from bzrlib.trace import mutter
32
from bzrlib.transport import Transport, Server
33
import bzrlib.urlutils as urlutils
19
from bzrlib.transport import Transport, register_transport, \
20
TransportError, NoSuchFile, FileExists
23
class LocalTransportError(TransportError):
36
26
class LocalTransport(Transport):
37
27
"""This is the transport agent for local filesystem access."""
39
29
def __init__(self, base):
40
30
"""Set the base path where files will be stored."""
41
if not base.startswith('file://'):
42
warn("Instantiating LocalTransport with a filesystem path"
43
" is deprecated as of bzr 0.8."
44
" Please use bzrlib.transport.get_transport()"
45
" or pass in a file:// url.",
49
base = urlutils.local_path_to_url(base)
52
super(LocalTransport, self).__init__(base)
53
self._local_base = urlutils.local_path_from_url(base)
54
## mutter("_local_base: %r => %r", base, self._local_base)
31
if base.startswith('file://'):
33
# realpath is incompatible with symlinks. When we traverse
34
# up we might be able to normpath stuff. RBC 20051003
35
super(LocalTransport, self).__init__(
36
os.path.normpath(os.path.abspath(base)))
56
38
def should_cache(self):
67
49
return LocalTransport(self.abspath(offset))
69
def _abspath(self, relative_reference):
70
"""Return a path for use in os calls.
72
Several assumptions are made:
73
- relative_reference does not contain '..'
74
- relative_reference is url escaped.
76
if relative_reference in ('.', ''):
77
return self._local_base
78
return self._local_base + urlutils.unescape(relative_reference)
80
51
def abspath(self, relpath):
81
"""Return the full url to the given relative URL."""
82
# TODO: url escape the result. RBC 20060523.
83
assert isinstance(relpath, basestring), (type(relpath), relpath)
84
# jam 20060426 Using normpath on the real path, because that ensures
85
# proper handling of stuff like
86
path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
87
return urlutils.local_path_to_url(path)
89
def local_abspath(self, relpath):
90
"""Transform the given relative path URL into the actual path on disk
92
This function only exists for the LocalTransport, since it is
93
the only one that has direct local access.
94
This is mostly for stuff like WorkingTree which needs to know
95
the local working directory.
97
This function is quite expensive: it calls realpath which resolves
52
"""Return the full url to the given relative path.
53
This can be supplied with a string or a list
100
absurl = self.abspath(relpath)
101
# mutter(u'relpath %s => base: %s, absurl %s', relpath, self.base, absurl)
102
return urlutils.local_path_from_url(absurl)
55
if isinstance(relpath, basestring):
57
return os.path.join(self.base, *relpath)
104
59
def relpath(self, abspath):
105
60
"""Return the local path portion from a given absolute path.
110
return urlutils.file_relpath(
111
urlutils.strip_trailing_slash(self.base),
112
urlutils.strip_trailing_slash(abspath))
62
from bzrlib.osutils import relpath
63
return relpath(self.base, abspath)
114
65
def has(self, relpath):
115
return os.access(self._abspath(relpath), os.F_OK)
66
return os.access(self.abspath(relpath), os.F_OK)
117
68
def get(self, relpath):
118
69
"""Get the file at the given relative path.
120
71
:param relpath: The relative path to the file
123
path = self._abspath(relpath)
74
path = self.abspath(relpath)
124
75
return open(path, 'rb')
125
except (IOError, OSError),e:
126
self._translate_error(e, path)
128
def put(self, relpath, f, mode=None):
77
if e.errno in (errno.ENOENT, errno.ENOTDIR):
78
raise NoSuchFile('File or directory %r does not exist' % path, orig_error=e)
79
raise LocalTransportError(orig_error=e)
81
def get_partial(self, relpath, start, length=None):
82
"""Get just part of a file.
84
:param relpath: Path to the file, relative to base
85
:param start: The starting position to read from
86
:param length: The length to read. A length of None indicates
87
read to the end of the file.
88
:return: A file-like object containing at least the specified bytes.
89
Some implementations may return objects which can be read
90
past this length, but this is not guaranteed.
92
# LocalTransport.get_partial() doesn't care about the length
93
# argument, because it is using a local file, and thus just
94
# returns the file seek'ed to the appropriate location.
96
path = self.abspath(relpath)
101
if e.errno == errno.ENOENT:
102
raise NoSuchFile('File %r does not exist' % path, orig_error=e)
103
raise LocalTransportError(orig_error=e)
105
def put(self, relpath, f):
129
106
"""Copy the file-like or string object into the location.
131
108
:param relpath: Location to put the contents, relative to base.
134
111
from bzrlib.atomicfile import AtomicFile
138
path = self._abspath(relpath)
139
check_legal_path(path)
140
fp = AtomicFile(path, 'wb', new_mode=mode)
141
except (IOError, OSError),e:
142
self._translate_error(e, path)
114
path = self.abspath(relpath)
115
fp = AtomicFile(path, 'wb')
117
if e.errno == errno.ENOENT:
118
raise NoSuchFile('File %r does not exist' % path, orig_error=e)
119
raise LocalTransportError(orig_error=e)
144
121
self._pump(f, fp)
149
def iter_files_recursive(self):
150
"""Iter the relative paths of files in the transports sub-tree."""
151
queue = list(self.list_dir(u'.'))
153
relpath = queue.pop(0)
154
st = self.stat(relpath)
155
if S_ISDIR(st[ST_MODE]):
156
for i, basename in enumerate(self.list_dir(relpath)):
157
queue.insert(i, relpath+'/'+basename)
161
def mkdir(self, relpath, mode=None):
126
def mkdir(self, relpath):
162
127
"""Create a directory at the given path."""
165
path = self._abspath(relpath)
169
except (IOError, OSError),e:
170
self._translate_error(e, path)
129
os.mkdir(self.abspath(relpath))
131
if e.errno == errno.EEXIST:
132
raise FileExists(orig_error=e)
133
elif e.errno == errno.ENOENT:
134
raise NoSuchFile(orig_error=e)
135
raise LocalTransportError(orig_error=e)
172
def append(self, relpath, f, mode=None):
173
"""Append the text in the file-like object into the final location."""
174
abspath = self._abspath(relpath)
176
fp = open(abspath, 'ab')
177
# FIXME should we really be chmodding every time ? RBC 20060523
179
os.chmod(abspath, mode)
180
except (IOError, OSError),e:
181
self._translate_error(e, relpath)
182
# win32 workaround (tell on an unwritten file returns 0)
137
def append(self, relpath, f):
138
"""Append the text in the file-like object into the final
141
fp = open(self.abspath(relpath), 'ab')
185
142
self._pump(f, fp)
188
144
def copy(self, rel_from, rel_to):
189
145
"""Copy the item at rel_from to the location at rel_to"""
190
path_from = self._abspath(rel_from)
191
path_to = self._abspath(rel_to)
147
path_from = self.abspath(rel_from)
148
path_to = self.abspath(rel_to)
193
150
shutil.copy(path_from, path_to)
194
except (IOError, OSError),e:
195
# TODO: What about path_to?
196
self._translate_error(e, path_from)
198
def rename(self, rel_from, rel_to):
199
path_from = self._abspath(rel_from)
201
# *don't* call bzrlib.osutils.rename, because we want to
202
# detect errors on rename
203
os.rename(path_from, self._abspath(rel_to))
204
except (IOError, OSError),e:
205
# TODO: What about path_to?
206
self._translate_error(e, path_from)
152
raise LocalTransportError(orig_error=e)
208
154
def move(self, rel_from, rel_to):
209
155
"""Move the item at rel_from to the location at rel_to"""
210
path_from = self._abspath(rel_from)
211
path_to = self._abspath(rel_to)
156
path_from = self.abspath(rel_from)
157
path_to = self.abspath(rel_to)
214
# this version will delete the destination if necessary
215
rename(path_from, path_to)
216
except (IOError, OSError),e:
217
# TODO: What about path_to?
218
self._translate_error(e, path_from)
160
os.rename(path_from, path_to)
162
raise LocalTransportError(orig_error=e)
220
164
def delete(self, relpath):
221
165
"""Delete the item at relpath"""
224
path = self._abspath(relpath)
226
except (IOError, OSError),e:
227
self._translate_error(e, path)
167
os.remove(self.abspath(relpath))
169
raise LocalTransportError(orig_error=e)
229
def copy_to(self, relpaths, other, mode=None, pb=None):
171
def copy_to(self, relpaths, other, pb=None):
230
172
"""Copy a set of entries from self into another Transport.
232
174
:param relpaths: A list/generator of entries to be copied.
235
177
# Both from & to are on the local filesystem
236
178
# Unfortunately, I can't think of anything faster than just
237
179
# copying them across, one by one :(
238
182
total = self._get_total(relpaths)
240
184
for path in relpaths:
241
185
self._update_pb(pb, 'copy-to', count, total)
243
mypath = self._abspath(path)
244
otherpath = other._abspath(path)
245
shutil.copy(mypath, otherpath)
247
os.chmod(otherpath, mode)
248
except (IOError, OSError),e:
249
self._translate_error(e, path)
186
shutil.copy(self.abspath(path), other.abspath(path))
253
return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
190
return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
255
192
def listable(self):
256
193
"""See Transport.listable."""
261
198
WARNING: many transports do not support this, so trying avoid using
262
199
it if at all possible.
264
path = self._abspath(relpath)
266
return [urlutils.escape(entry) for entry in os.listdir(path)]
267
except (IOError, OSError), e:
268
self._translate_error(e, path)
202
return os.listdir(self.abspath(relpath))
204
raise LocalTransportError(orig_error=e)
270
206
def stat(self, relpath):
271
207
"""Return the stat information for a file.
275
path = self._abspath(relpath)
277
except (IOError, OSError),e:
278
self._translate_error(e, path)
210
return os.stat(self.abspath(relpath))
212
raise LocalTransportError(orig_error=e)
280
214
def lock_read(self, relpath):
281
215
"""Lock the given file for shared (read) access.
282
216
:return: A lock object, which should be passed to Transport.unlock()
284
218
from bzrlib.lock import ReadLock
287
path = self._abspath(relpath)
288
return ReadLock(path)
289
except (IOError, OSError), e:
290
self._translate_error(e, path)
219
return ReadLock(self.abspath(relpath))
292
221
def lock_write(self, relpath):
293
222
"""Lock the given file for exclusive (write) access.
296
225
:return: A lock object, which should be passed to Transport.unlock()
298
227
from bzrlib.lock import WriteLock
299
return WriteLock(self._abspath(relpath))
301
def rmdir(self, relpath):
302
"""See Transport.rmdir."""
305
path = self._abspath(relpath)
307
except (IOError, OSError),e:
308
self._translate_error(e, path)
310
def _can_roundtrip_unix_modebits(self):
311
if sys.platform == 'win32':
318
class LocalRelpathServer(Server):
319
"""A pretend server for local transports, using relpaths."""
322
"""See Transport.Server.get_url."""
326
class LocalAbspathServer(Server):
327
"""A pretend server for local transports, using absolute paths."""
330
"""See Transport.Server.get_url."""
331
return os.path.abspath("")
334
class LocalURLServer(Server):
335
"""A pretend server for local transports, using file:// urls."""
338
"""See Transport.Server.get_url."""
339
return urlutils.local_path_to_url('')
342
def get_test_permutations():
343
"""Return the permutations to be used in testing."""
344
return [(LocalTransport, LocalRelpathServer),
345
(LocalTransport, LocalAbspathServer),
346
(LocalTransport, LocalURLServer),
228
return WriteLock(self.abspath(relpath))
230
# If nothing else matches, try the LocalTransport
231
register_transport(None, LocalTransport)
232
register_transport('file://', LocalTransport)