1
# Copyright (C) 2005, 2006 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
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.
25
from stat import ST_MODE, S_ISDIR, ST_SIZE, S_IMODE
32
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename,
33
check_legal_path, rmtree)
34
from bzrlib.symbol_versioning import warn
35
from bzrlib.trace import mutter
36
from bzrlib.transport import Transport, Server
39
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY
42
class LocalTransport(Transport):
43
"""This is the transport agent for local filesystem access."""
45
def __init__(self, base):
46
"""Set the base path where files will be stored."""
47
if not base.startswith('file://'):
48
warn("Instantiating LocalTransport with a filesystem path"
49
" is deprecated as of bzr 0.8."
50
" Please use bzrlib.transport.get_transport()"
51
" or pass in a file:// url.",
55
base = urlutils.local_path_to_url(base)
58
super(LocalTransport, self).__init__(base)
59
self._local_base = urlutils.local_path_from_url(base)
60
self._get_default_modes()
62
def _get_default_modes(self):
63
"""Figure out what the default file and directory modes are"""
64
umask = osutils.get_umask()
65
self._default_file_mode = 0666 & ~umask
66
self._default_dir_mode = 0777 & ~umask
68
def should_cache(self):
71
def clone(self, offset=None):
72
"""Return a new LocalTransport with root at self.base + offset
73
Because the local filesystem does not require a connection,
74
we can just return a new object.
77
return LocalTransport(self.base)
79
return LocalTransport(self.abspath(offset))
81
def _abspath(self, relative_reference):
82
"""Return a path for use in os calls.
84
Several assumptions are made:
85
- relative_reference does not contain '..'
86
- relative_reference is url escaped.
88
if relative_reference in ('.', ''):
89
return self._local_base
90
return self._local_base + urlutils.unescape(relative_reference)
92
def abspath(self, relpath):
93
"""Return the full url to the given relative URL."""
94
# TODO: url escape the result. RBC 20060523.
95
assert isinstance(relpath, basestring), (type(relpath), relpath)
96
# jam 20060426 Using normpath on the real path, because that ensures
97
# proper handling of stuff like
98
path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
99
return urlutils.local_path_to_url(path)
101
def local_abspath(self, relpath):
102
"""Transform the given relative path URL into the actual path on disk
104
This function only exists for the LocalTransport, since it is
105
the only one that has direct local access.
106
This is mostly for stuff like WorkingTree which needs to know
107
the local working directory.
109
This function is quite expensive: it calls realpath which resolves
112
absurl = self.abspath(relpath)
113
# mutter(u'relpath %s => base: %s, absurl %s', relpath, self.base, absurl)
114
return urlutils.local_path_from_url(absurl)
116
def relpath(self, abspath):
117
"""Return the local path portion from a given absolute path.
122
return urlutils.file_relpath(
123
urlutils.strip_trailing_slash(self.base),
124
urlutils.strip_trailing_slash(abspath))
126
def has(self, relpath):
127
return os.access(self._abspath(relpath), os.F_OK)
129
def get(self, relpath):
130
"""Get the file at the given relative path.
132
:param relpath: The relative path to the file
135
path = self._abspath(relpath)
136
return open(path, 'rb')
137
except (IOError, OSError),e:
138
self._translate_error(e, path)
140
def put(self, relpath, f, mode=None):
141
"""Copy the file-like or string object into the location.
143
:param relpath: Location to put the contents, relative to base.
144
:param f: File-like or string object.
146
from bzrlib.atomicfile import AtomicFile
149
mode = self._default_file_mode
152
path = self._abspath(relpath)
153
check_legal_path(path)
154
fp = AtomicFile(path, 'wb', new_mode=mode)
155
except (IOError, OSError),e:
156
self._translate_error(e, path)
163
def iter_files_recursive(self):
164
"""Iter the relative paths of files in the transports sub-tree."""
165
queue = list(self.list_dir(u'.'))
167
relpath = queue.pop(0)
168
st = self.stat(relpath)
169
if S_ISDIR(st[ST_MODE]):
170
for i, basename in enumerate(self.list_dir(relpath)):
171
queue.insert(i, relpath+'/'+basename)
175
def mkdir(self, relpath, mode=None):
176
"""Create a directory at the given path."""
180
local_mode = self._default_dir_mode
183
path = self._abspath(relpath)
184
os.mkdir(path, local_mode)
186
# It is probably faster to just do the chmod, rather than
187
# doing a stat, and then trying to compare
189
except (IOError, OSError),e:
190
self._translate_error(e, path)
192
def append(self, relpath, f, mode=None):
193
"""Append the text in the file-like object into the final location."""
194
abspath = self._abspath(relpath)
196
local_mode = self._default_file_mode
200
fd = os.open(abspath, _append_flags, local_mode)
201
except (IOError, OSError),e:
202
self._translate_error(e, relpath)
206
if mode is not None and mode != S_IMODE(st.st_mode):
207
# Because of umask, we may still need to chmod the file.
208
# But in the general case, we won't have to
209
os.chmod(abspath, mode)
210
# TODO: make a raw FD version of _pump ?
211
self._pump_to_fd(f, fd)
216
def _pump_to_fd(self, fromfile, to_fd):
217
"""Copy contents of one file to another."""
220
b = fromfile.read(BUFSIZE)
225
def copy(self, rel_from, rel_to):
226
"""Copy the item at rel_from to the location at rel_to"""
227
path_from = self._abspath(rel_from)
228
path_to = self._abspath(rel_to)
230
shutil.copy(path_from, path_to)
231
except (IOError, OSError),e:
232
# TODO: What about path_to?
233
self._translate_error(e, path_from)
235
def rename(self, rel_from, rel_to):
236
path_from = self._abspath(rel_from)
238
# *don't* call bzrlib.osutils.rename, because we want to
239
# detect errors on rename
240
os.rename(path_from, self._abspath(rel_to))
241
except (IOError, OSError),e:
242
# TODO: What about path_to?
243
self._translate_error(e, path_from)
245
def move(self, rel_from, rel_to):
246
"""Move the item at rel_from to the location at rel_to"""
247
path_from = self._abspath(rel_from)
248
path_to = self._abspath(rel_to)
251
# this version will delete the destination if necessary
252
rename(path_from, path_to)
253
except (IOError, OSError),e:
254
# TODO: What about path_to?
255
self._translate_error(e, path_from)
257
def delete(self, relpath):
258
"""Delete the item at relpath"""
261
path = self._abspath(relpath)
263
except (IOError, OSError),e:
264
self._translate_error(e, path)
266
def copy_to(self, relpaths, other, mode=None, pb=None):
267
"""Copy a set of entries from self into another Transport.
269
:param relpaths: A list/generator of entries to be copied.
271
if isinstance(other, LocalTransport):
272
# Both from & to are on the local filesystem
273
# Unfortunately, I can't think of anything faster than just
274
# copying them across, one by one :(
275
total = self._get_total(relpaths)
277
for path in relpaths:
278
self._update_pb(pb, 'copy-to', count, total)
280
mypath = self._abspath(path)
281
otherpath = other._abspath(path)
282
shutil.copy(mypath, otherpath)
284
os.chmod(otherpath, mode)
285
except (IOError, OSError),e:
286
self._translate_error(e, path)
290
return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
293
"""See Transport.listable."""
296
def list_dir(self, relpath):
297
"""Return a list of all files at the given location.
298
WARNING: many transports do not support this, so trying avoid using
299
it if at all possible.
301
path = self._abspath(relpath)
303
return [urlutils.escape(entry) for entry in os.listdir(path)]
304
except (IOError, OSError), e:
305
self._translate_error(e, path)
307
def stat(self, relpath):
308
"""Return the stat information for a file.
312
path = self._abspath(relpath)
314
except (IOError, OSError),e:
315
self._translate_error(e, path)
317
def lock_read(self, relpath):
318
"""Lock the given file for shared (read) access.
319
:return: A lock object, which should be passed to Transport.unlock()
321
from bzrlib.lock import ReadLock
324
path = self._abspath(relpath)
325
return ReadLock(path)
326
except (IOError, OSError), e:
327
self._translate_error(e, path)
329
def lock_write(self, relpath):
330
"""Lock the given file for exclusive (write) access.
331
WARNING: many transports do not support this, so trying avoid using it
333
:return: A lock object, which should be passed to Transport.unlock()
335
from bzrlib.lock import WriteLock
336
return WriteLock(self._abspath(relpath))
338
def rmdir(self, relpath):
339
"""See Transport.rmdir."""
342
path = self._abspath(relpath)
344
except (IOError, OSError),e:
345
self._translate_error(e, path)
347
def _can_roundtrip_unix_modebits(self):
348
if sys.platform == 'win32':
355
class LocalRelpathServer(Server):
356
"""A pretend server for local transports, using relpaths."""
359
"""See Transport.Server.get_url."""
363
class LocalAbspathServer(Server):
364
"""A pretend server for local transports, using absolute paths."""
367
"""See Transport.Server.get_url."""
368
return os.path.abspath("")
371
class LocalURLServer(Server):
372
"""A pretend server for local transports, using file:// urls."""
375
"""See Transport.Server.get_url."""
376
return urlutils.local_path_to_url('')
379
def get_test_permutations():
380
"""Return the permutations to be used in testing."""
381
return [(LocalTransport, LocalRelpathServer),
382
(LocalTransport, LocalAbspathServer),
383
(LocalTransport, LocalURLServer),