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
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
36
class LocalTransport(Transport):
37
"""This is the transport agent for local filesystem access."""
39
def __init__(self, base):
40
"""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)
55
def should_cache(self):
58
def clone(self, offset=None):
59
"""Return a new LocalTransport with root at self.base + offset
60
Because the local filesystem does not require a connection,
61
we can just return a new object.
64
return LocalTransport(self.base)
66
return LocalTransport(self.abspath(offset))
68
def _abspath(self, relative_reference):
69
"""Return a path for use in os calls.
71
Several assumptions are made:
72
- relative_reference does not contain '..'
73
- relative_reference is url escaped.
75
if relative_reference in ('.', ''):
76
return self._local_base
77
return self._local_base + urlutils.unescape(relative_reference)
79
def abspath(self, relpath):
80
"""Return the full url to the given relative URL."""
81
# TODO: url escape the result. RBC 20060523.
82
assert isinstance(relpath, basestring), (type(relpath), relpath)
83
# jam 20060426 Using normpath on the real path, because that ensures
84
# proper handling of stuff like
85
path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
86
return urlutils.local_path_to_url(path)
88
def local_abspath(self, relpath):
89
"""Transform the given relative path URL into the actual path on disk
91
This function only exists for the LocalTransport, since it is
92
the only one that has direct local access.
93
This is mostly for stuff like WorkingTree which needs to know
94
the local working directory.
96
This function is quite expensive: it calls realpath which resolves
99
absurl = self.abspath(relpath)
100
# mutter(u'relpath %s => base: %s, absurl %s', relpath, self.base, absurl)
101
return urlutils.local_path_from_url(absurl)
103
def relpath(self, abspath):
104
"""Return the local path portion from a given absolute path.
109
return urlutils.file_relpath(
110
urlutils.strip_trailing_slash(self.base),
111
urlutils.strip_trailing_slash(abspath))
113
def has(self, relpath):
114
return os.access(self._abspath(relpath), os.F_OK)
116
def get(self, relpath):
117
"""Get the file at the given relative path.
119
:param relpath: The relative path to the file
122
path = self._abspath(relpath)
123
return open(path, 'rb')
124
except (IOError, OSError),e:
125
self._translate_error(e, path)
127
def put(self, relpath, f, mode=None):
128
"""Copy the file-like or string object into the location.
130
:param relpath: Location to put the contents, relative to base.
131
:param f: File-like or string object.
133
from bzrlib.atomicfile import AtomicFile
137
path = self._abspath(relpath)
138
check_legal_path(path)
139
fp = AtomicFile(path, 'wb', new_mode=mode)
140
except (IOError, OSError),e:
141
self._translate_error(e, path)
148
def iter_files_recursive(self):
149
"""Iter the relative paths of files in the transports sub-tree."""
150
queue = list(self.list_dir(u'.'))
152
relpath = queue.pop(0)
153
st = self.stat(relpath)
154
if S_ISDIR(st[ST_MODE]):
155
for i, basename in enumerate(self.list_dir(relpath)):
156
queue.insert(i, relpath+'/'+basename)
160
def mkdir(self, relpath, mode=None):
161
"""Create a directory at the given path."""
164
path = self._abspath(relpath)
168
except (IOError, OSError),e:
169
self._translate_error(e, path)
171
def append(self, relpath, f, mode=None):
172
"""Append the text in the file-like object into the final location."""
173
abspath = self._abspath(relpath)
175
fp = open(abspath, 'ab')
176
# FIXME should we really be chmodding every time ? RBC 20060523
178
os.chmod(abspath, mode)
179
except (IOError, OSError),e:
180
self._translate_error(e, relpath)
181
# win32 workaround (tell on an unwritten file returns 0)
187
def copy(self, rel_from, rel_to):
188
"""Copy the item at rel_from to the location at rel_to"""
189
path_from = self._abspath(rel_from)
190
path_to = self._abspath(rel_to)
192
shutil.copy(path_from, path_to)
193
except (IOError, OSError),e:
194
# TODO: What about path_to?
195
self._translate_error(e, path_from)
197
def rename(self, rel_from, rel_to):
198
path_from = self._abspath(rel_from)
200
# *don't* call bzrlib.osutils.rename, because we want to
201
# detect errors on rename
202
os.rename(path_from, self._abspath(rel_to))
203
except (IOError, OSError),e:
204
# TODO: What about path_to?
205
self._translate_error(e, path_from)
207
def move(self, rel_from, rel_to):
208
"""Move the item at rel_from to the location at rel_to"""
209
path_from = self._abspath(rel_from)
210
path_to = self._abspath(rel_to)
213
# this version will delete the destination if necessary
214
rename(path_from, path_to)
215
except (IOError, OSError),e:
216
# TODO: What about path_to?
217
self._translate_error(e, path_from)
219
def delete(self, relpath):
220
"""Delete the item at relpath"""
223
path = self._abspath(relpath)
225
except (IOError, OSError),e:
226
self._translate_error(e, path)
228
def copy_to(self, relpaths, other, mode=None, pb=None):
229
"""Copy a set of entries from self into another Transport.
231
:param relpaths: A list/generator of entries to be copied.
233
if isinstance(other, LocalTransport):
234
# Both from & to are on the local filesystem
235
# Unfortunately, I can't think of anything faster than just
236
# copying them across, one by one :(
237
total = self._get_total(relpaths)
239
for path in relpaths:
240
self._update_pb(pb, 'copy-to', count, total)
242
mypath = self._abspath(path)
243
otherpath = other._abspath(path)
244
shutil.copy(mypath, otherpath)
246
os.chmod(otherpath, mode)
247
except (IOError, OSError),e:
248
self._translate_error(e, path)
252
return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
255
"""See Transport.listable."""
258
def list_dir(self, relpath):
259
"""Return a list of all files at the given location.
260
WARNING: many transports do not support this, so trying avoid using
261
it if at all possible.
263
path = self._abspath(relpath)
265
return [urlutils.escape(entry) for entry in os.listdir(path)]
266
except (IOError, OSError), e:
267
self._translate_error(e, path)
269
def stat(self, relpath):
270
"""Return the stat information for a file.
274
path = self._abspath(relpath)
276
except (IOError, OSError),e:
277
self._translate_error(e, path)
279
def lock_read(self, relpath):
280
"""Lock the given file for shared (read) access.
281
:return: A lock object, which should be passed to Transport.unlock()
283
from bzrlib.lock import ReadLock
286
path = self._abspath(relpath)
287
return ReadLock(path)
288
except (IOError, OSError), e:
289
self._translate_error(e, path)
291
def lock_write(self, relpath):
292
"""Lock the given file for exclusive (write) access.
293
WARNING: many transports do not support this, so trying avoid using it
295
:return: A lock object, which should be passed to Transport.unlock()
297
from bzrlib.lock import WriteLock
298
return WriteLock(self._abspath(relpath))
300
def rmdir(self, relpath):
301
"""See Transport.rmdir."""
304
path = self._abspath(relpath)
306
except (IOError, OSError),e:
307
self._translate_error(e, path)
309
def _can_roundtrip_unix_modebits(self):
310
if sys.platform == 'win32':
317
class ScratchTransport(LocalTransport):
318
"""A transport that works in a temporary dir and cleans up after itself.
320
The dir only exists for the lifetime of the Python object.
321
Obviously you should not put anything precious in it.
324
def __init__(self, base=None):
326
base = tempfile.mkdtemp()
327
super(ScratchTransport, self).__init__(base)
330
rmtree(self.base, ignore_errors=True)
331
mutter("%r destroyed" % self)
334
class LocalRelpathServer(Server):
335
"""A pretend server for local transports, using relpaths."""
338
"""See Transport.Server.get_url."""
342
class LocalAbspathServer(Server):
343
"""A pretend server for local transports, using absolute paths."""
346
"""See Transport.Server.get_url."""
347
return os.path.abspath("")
350
class LocalURLServer(Server):
351
"""A pretend server for local transports, using file:// urls."""
354
"""See Transport.Server.get_url."""
355
return urlutils.local_path_to_url('')
358
def get_test_permutations():
359
"""Return the permutations to be used in testing."""
360
return [(LocalTransport, LocalRelpathServer),
361
(LocalTransport, LocalAbspathServer),
362
(LocalTransport, LocalURLServer),