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
16
"""Implementation of Transport for the local filesystem.
17
"""Transport for the local filesystem.
19
This is a fairly thin wrapper on regular file IO.
19
from bzrlib.transport import Transport, register_transport, \
20
TransportError, NoSuchFile, FileExists
23
class LocalTransportError(TransportError):
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
26
36
class LocalTransport(Transport):
27
37
"""This is the transport agent for local filesystem access."""
29
39
def __init__(self, base):
30
40
"""Set the base path where files will be stored."""
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)))
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)
38
56
def should_cache(self):
49
67
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)
51
80
def abspath(self, relpath):
52
"""Return the full url to the given relative path.
53
This can be supplied with a string or a list
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
55
if isinstance(relpath, basestring):
57
return os.path.join(self.base, *relpath)
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)
59
104
def relpath(self, abspath):
60
105
"""Return the local path portion from a given absolute path.
62
from bzrlib.branch import _relpath
63
return _relpath(self.base, abspath)
110
return urlutils.file_relpath(
111
urlutils.strip_trailing_slash(self.base),
112
urlutils.strip_trailing_slash(abspath))
65
114
def has(self, relpath):
66
return os.access(self.abspath(relpath), os.F_OK)
115
return os.access(self._abspath(relpath), os.F_OK)
68
117
def get(self, relpath):
69
118
"""Get the file at the given relative path.
71
120
:param relpath: The relative path to the file
74
path = self.abspath(relpath)
123
path = self._abspath(relpath)
75
124
return open(path, 'rb')
77
if e.errno == errno.ENOENT:
78
raise NoSuchFile('File %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):
125
except (IOError, OSError),e:
126
self._translate_error(e, path)
128
def put(self, relpath, f, mode=None):
106
129
"""Copy the file-like or string object into the location.
108
131
:param relpath: Location to put the contents, relative to base.
111
134
from bzrlib.atomicfile import AtomicFile
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)
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)
121
144
self._pump(f, fp)
126
def mkdir(self, relpath):
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):
127
162
"""Create a directory at the given 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)
165
path = self._abspath(relpath)
169
except (IOError, OSError),e:
170
self._translate_error(e, path)
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')
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)
142
185
self._pump(f, fp)
144
188
def copy(self, rel_from, rel_to):
145
189
"""Copy the item at rel_from to the location at rel_to"""
147
path_from = self.abspath(rel_from)
148
path_to = self.abspath(rel_to)
190
path_from = self._abspath(rel_from)
191
path_to = self._abspath(rel_to)
150
193
shutil.copy(path_from, path_to)
152
raise LocalTransportError(orig_error=e)
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)
154
208
def move(self, rel_from, rel_to):
155
209
"""Move the item at rel_from to the location at rel_to"""
156
path_from = self.abspath(rel_from)
157
path_to = self.abspath(rel_to)
210
path_from = self._abspath(rel_from)
211
path_to = self._abspath(rel_to)
160
os.rename(path_from, path_to)
162
raise LocalTransportError(orig_error=e)
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)
164
220
def delete(self, relpath):
165
221
"""Delete the item at relpath"""
167
os.remove(self.abspath(relpath))
169
raise LocalTransportError(orig_error=e)
224
path = self._abspath(relpath)
226
except (IOError, OSError),e:
227
self._translate_error(e, path)
171
def copy_to(self, relpaths, other, pb=None):
229
def copy_to(self, relpaths, other, mode=None, pb=None):
172
230
"""Copy a set of entries from self into another Transport.
174
232
:param relpaths: A list/generator of entries to be copied.
177
235
# Both from & to are on the local filesystem
178
236
# Unfortunately, I can't think of anything faster than just
179
237
# copying them across, one by one :(
182
238
total = self._get_total(relpaths)
184
240
for path in relpaths:
185
241
self._update_pb(pb, 'copy-to', count, total)
186
shutil.copy(self.abspath(path), other.abspath(path))
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)
190
return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
253
return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
192
255
def listable(self):
193
256
"""See Transport.listable."""
198
261
WARNING: many transports do not support this, so trying avoid using
199
262
it if at all possible.
264
path = self._abspath(relpath)
202
return os.listdir(self.abspath(relpath))
204
raise LocalTransportError(orig_error=e)
266
return [urlutils.escape(entry) for entry in os.listdir(path)]
267
except (IOError, OSError), e:
268
self._translate_error(e, path)
206
270
def stat(self, relpath):
207
271
"""Return the stat information for a file.
210
return os.stat(self.abspath(relpath))
212
raise LocalTransportError(orig_error=e)
275
path = self._abspath(relpath)
277
except (IOError, OSError),e:
278
self._translate_error(e, path)
214
280
def lock_read(self, relpath):
215
281
"""Lock the given file for shared (read) access.
216
282
:return: A lock object, which should be passed to Transport.unlock()
218
284
from bzrlib.lock import ReadLock
219
return ReadLock(self.abspath(relpath))
287
path = self._abspath(relpath)
288
return ReadLock(path)
289
except (IOError, OSError), e:
290
self._translate_error(e, path)
221
292
def lock_write(self, relpath):
222
293
"""Lock the given file for exclusive (write) access.
225
296
:return: A lock object, which should be passed to Transport.unlock()
227
298
from bzrlib.lock import WriteLock
228
return WriteLock(self.abspath(relpath))
230
# If nothing else matches, try the LocalTransport
231
register_transport(None, LocalTransport)
232
register_transport('file://', LocalTransport)
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),