1
# Copyright (C) 2005, 2006 Canonical Ltd
1
# Copyright (C) 2005 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
"""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.branch 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 == 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):
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)
178
fp = open(abspath, 'ab')
179
# FIXME should we really be chmodding every time ? RBC 20060523
181
os.chmod(abspath, mode)
182
except (IOError, OSError),e:
183
self._translate_error(e, relpath)
184
# 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')
193
144
def copy(self, rel_from, rel_to):
194
145
"""Copy the item at rel_from to the location at rel_to"""
195
path_from = self._abspath(rel_from)
196
path_to = self._abspath(rel_to)
147
path_from = self.abspath(rel_from)
148
path_to = self.abspath(rel_to)
198
150
shutil.copy(path_from, path_to)
199
except (IOError, OSError),e:
200
# TODO: What about path_to?
201
self._translate_error(e, path_from)
203
def rename(self, rel_from, rel_to):
204
path_from = self._abspath(rel_from)
206
# *don't* call bzrlib.osutils.rename, because we want to
207
# detect errors on rename
208
os.rename(path_from, self._abspath(rel_to))
209
except (IOError, OSError),e:
210
# TODO: What about path_to?
211
self._translate_error(e, path_from)
152
raise LocalTransportError(orig_error=e)
213
154
def move(self, rel_from, rel_to):
214
155
"""Move the item at rel_from to the location at rel_to"""
215
path_from = self._abspath(rel_from)
216
path_to = self._abspath(rel_to)
156
path_from = self.abspath(rel_from)
157
path_to = self.abspath(rel_to)
219
# this version will delete the destination if necessary
220
rename(path_from, path_to)
221
except (IOError, OSError),e:
222
# TODO: What about path_to?
223
self._translate_error(e, path_from)
160
os.rename(path_from, path_to)
162
raise LocalTransportError(orig_error=e)
225
164
def delete(self, relpath):
226
165
"""Delete the item at relpath"""
229
path = self._abspath(relpath)
231
except (IOError, OSError),e:
232
self._translate_error(e, path)
167
os.remove(self.abspath(relpath))
169
raise LocalTransportError(orig_error=e)
234
def copy_to(self, relpaths, other, mode=None, pb=None):
171
def copy_to(self, relpaths, other, pb=None):
235
172
"""Copy a set of entries from self into another Transport.
237
174
:param relpaths: A list/generator of entries to be copied.
240
177
# Both from & to are on the local filesystem
241
178
# Unfortunately, I can't think of anything faster than just
242
179
# copying them across, one by one :(
243
182
total = self._get_total(relpaths)
245
184
for path in relpaths:
246
185
self._update_pb(pb, 'copy-to', count, total)
248
mypath = self._abspath(path)
249
otherpath = other._abspath(path)
250
shutil.copy(mypath, otherpath)
252
os.chmod(otherpath, mode)
253
except (IOError, OSError),e:
254
self._translate_error(e, path)
186
shutil.copy(self.abspath(path), other.abspath(path))
258
return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
190
return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
260
192
def listable(self):
261
193
"""See Transport.listable."""
266
198
WARNING: many transports do not support this, so trying avoid using
267
199
it if at all possible.
269
path = self._abspath(relpath)
271
return [urlutils.escape(entry) for entry in os.listdir(path)]
272
except (IOError, OSError), e:
273
self._translate_error(e, path)
202
return os.listdir(self.abspath(relpath))
204
raise LocalTransportError(orig_error=e)
275
206
def stat(self, relpath):
276
207
"""Return the stat information for a file.
280
path = self._abspath(relpath)
282
except (IOError, OSError),e:
283
self._translate_error(e, path)
210
return os.stat(self.abspath(relpath))
212
raise LocalTransportError(orig_error=e)
285
214
def lock_read(self, relpath):
286
215
"""Lock the given file for shared (read) access.
287
216
:return: A lock object, which should be passed to Transport.unlock()
289
218
from bzrlib.lock import ReadLock
292
path = self._abspath(relpath)
293
return ReadLock(path)
294
except (IOError, OSError), e:
295
self._translate_error(e, path)
219
return ReadLock(self.abspath(relpath))
297
221
def lock_write(self, relpath):
298
222
"""Lock the given file for exclusive (write) access.
301
225
:return: A lock object, which should be passed to Transport.unlock()
303
227
from bzrlib.lock import WriteLock
304
return WriteLock(self._abspath(relpath))
306
def rmdir(self, relpath):
307
"""See Transport.rmdir."""
310
path = self._abspath(relpath)
312
except (IOError, OSError),e:
313
self._translate_error(e, path)
315
def _can_roundtrip_unix_modebits(self):
316
if sys.platform == 'win32':
323
class LocalRelpathServer(Server):
324
"""A pretend server for local transports, using relpaths."""
327
"""See Transport.Server.get_url."""
331
class LocalAbspathServer(Server):
332
"""A pretend server for local transports, using absolute paths."""
335
"""See Transport.Server.get_url."""
336
return os.path.abspath("")
339
class LocalURLServer(Server):
340
"""A pretend server for local transports, using file:// urls."""
343
"""See Transport.Server.get_url."""
344
return urlutils.local_path_to_url('')
347
def get_test_permutations():
348
"""Return the permutations to be used in testing."""
349
return [(LocalTransport, LocalRelpathServer),
350
(LocalTransport, LocalAbspathServer),
351
(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)