1
# Copyright (C) 2005 Canonical Ltd
1
# Copyright (C) 2005, 2006 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
17
"""Transport for the local filesystem.
19
This is a fairly thin wrapper on regular file IO."""
19
This is a fairly thin wrapper on regular file IO.
23
from stat import ST_MODE, S_ISDIR, ST_SIZE
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
27
35
from bzrlib.trace import mutter
28
from bzrlib.transport import Transport
29
from bzrlib.osutils import abspath, realpath, normpath, pathjoin, rename
36
from bzrlib.transport import Transport, Server
39
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY
32
42
class LocalTransport(Transport):
35
45
def __init__(self, base):
36
46
"""Set the base path where files will be stored."""
37
if base.startswith('file://'):
39
# realpath is incompatible with symlinks. When we traverse
40
# up we might be able to normpath stuff. RBC 20051003
41
super(LocalTransport, self).__init__(normpath(abspath(base)))
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)
43
61
def should_cache(self):
54
72
return LocalTransport(self.abspath(offset))
74
def _abspath(self, relative_reference):
75
"""Return a path for use in os calls.
77
Several assumptions are made:
78
- relative_reference does not contain '..'
79
- relative_reference is url escaped.
81
if relative_reference in ('.', ''):
82
return self._local_base
83
return self._local_base + urlutils.unescape(relative_reference)
56
85
def abspath(self, relpath):
57
"""Return the full url to the given relative URL.
58
This can be supplied with a string or a list
86
"""Return the full url to the given relative URL."""
87
# TODO: url escape the result. RBC 20060523.
60
88
assert isinstance(relpath, basestring), (type(relpath), relpath)
61
return pathjoin(self.base, urllib.unquote(relpath))
89
# jam 20060426 Using normpath on the real path, because that ensures
90
# proper handling of stuff like
91
path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
92
return urlutils.local_path_to_url(path)
94
def local_abspath(self, relpath):
95
"""Transform the given relative path URL into the actual path on disk
97
This function only exists for the LocalTransport, since it is
98
the only one that has direct local access.
99
This is mostly for stuff like WorkingTree which needs to know
100
the local working directory.
102
This function is quite expensive: it calls realpath which resolves
105
absurl = self.abspath(relpath)
106
# mutter(u'relpath %s => base: %s, absurl %s', relpath, self.base, absurl)
107
return urlutils.local_path_from_url(absurl)
63
109
def relpath(self, abspath):
64
110
"""Return the local path portion from a given absolute path.
66
from bzrlib.osutils import relpath
67
112
if abspath is None:
69
return relpath(self.base, abspath)
115
return urlutils.file_relpath(
116
urlutils.strip_trailing_slash(self.base),
117
urlutils.strip_trailing_slash(abspath))
71
119
def has(self, relpath):
72
return os.access(self.abspath(relpath), os.F_OK)
120
return os.access(self._abspath(relpath), os.F_OK)
74
122
def get(self, relpath):
75
123
"""Get the file at the given relative path.
118
167
"""Create a directory at the given path."""
121
path = self.abspath(relpath)
171
# os.mkdir() will filter through umask
175
path = self._abspath(relpath)
176
os.mkdir(path, local_mode)
123
177
if mode is not None:
178
# It is probably faster to just do the chmod, rather than
179
# doing a stat, and then trying to compare
124
180
os.chmod(path, mode)
125
181
except (IOError, OSError),e:
126
182
self._translate_error(e, path)
128
def append(self, relpath, f):
129
"""Append the text in the file-like object into the final
132
fp = open(self.abspath(relpath), 'ab')
184
def append(self, relpath, f, mode=None):
185
"""Append the text in the file-like object into the final location."""
186
abspath = self._abspath(relpath)
188
# os.open() will automatically use the umask
193
fd = os.open(abspath, _append_flags, local_mode)
194
except (IOError, OSError),e:
195
self._translate_error(e, relpath)
199
if mode is not None and mode != S_IMODE(st.st_mode):
200
# Because of umask, we may still need to chmod the file.
201
# But in the general case, we won't have to
202
os.chmod(abspath, mode)
203
self._pump_to_fd(f, fd)
208
def _pump_to_fd(self, fromfile, to_fd):
209
"""Copy contents of one file to another."""
212
b = fromfile.read(BUFSIZE)
135
217
def copy(self, rel_from, rel_to):
136
218
"""Copy the item at rel_from to the location at rel_to"""
138
path_from = self.abspath(rel_from)
139
path_to = self.abspath(rel_to)
219
path_from = self._abspath(rel_from)
220
path_to = self._abspath(rel_to)
141
222
shutil.copy(path_from, path_to)
142
223
except (IOError, OSError),e:
143
224
# TODO: What about path_to?
144
225
self._translate_error(e, path_from)
227
def rename(self, rel_from, rel_to):
228
path_from = self._abspath(rel_from)
230
# *don't* call bzrlib.osutils.rename, because we want to
231
# detect errors on rename
232
os.rename(path_from, self._abspath(rel_to))
233
except (IOError, OSError),e:
234
# TODO: What about path_to?
235
self._translate_error(e, path_from)
146
237
def move(self, rel_from, rel_to):
147
238
"""Move the item at rel_from to the location at rel_to"""
148
path_from = self.abspath(rel_from)
149
path_to = self.abspath(rel_to)
239
path_from = self._abspath(rel_from)
240
path_to = self._abspath(rel_to)
243
# this version will delete the destination if necessary
152
244
rename(path_from, path_to)
153
245
except (IOError, OSError),e:
154
246
# TODO: What about path_to?
173
264
# Both from & to are on the local filesystem
174
265
# Unfortunately, I can't think of anything faster than just
175
266
# copying them across, one by one :(
178
267
total = self._get_total(relpaths)
180
269
for path in relpaths:
181
270
self._update_pb(pb, 'copy-to', count, total)
183
mypath = self.abspath(path)
184
otherpath = other.abspath(path)
272
mypath = self._abspath(path)
273
otherpath = other._abspath(path)
185
274
shutil.copy(mypath, otherpath)
186
275
if mode is not None:
187
276
os.chmod(otherpath, mode)
201
290
WARNING: many transports do not support this, so trying avoid using
202
291
it if at all possible.
293
path = self._abspath(relpath)
206
path = self.abspath(relpath)
207
return os.listdir(path)
208
except (IOError, OSError),e:
295
entries = os.listdir(path)
296
except (IOError, OSError), e:
209
297
self._translate_error(e, path)
298
return [urlutils.escape(entry) for entry in entries]
211
300
def stat(self, relpath):
212
301
"""Return the stat information for a file.
216
path = self.abspath(relpath)
305
path = self._abspath(relpath)
217
306
return os.stat(path)
218
307
except (IOError, OSError),e:
219
308
self._translate_error(e, path)
232
326
:return: A lock object, which should be passed to Transport.unlock()
234
328
from bzrlib.lock import WriteLock
235
return WriteLock(self.abspath(relpath))
238
class ScratchTransport(LocalTransport):
239
"""A transport that works in a temporary dir and cleans up after itself.
241
The dir only exists for the lifetime of the Python object.
242
Obviously you should not put anything precious in it.
245
def __init__(self, base=None):
247
base = tempfile.mkdtemp()
248
super(ScratchTransport, self).__init__(base)
251
shutil.rmtree(self.base, ignore_errors=True)
252
mutter("%r destroyed" % self)
329
return WriteLock(self._abspath(relpath))
331
def rmdir(self, relpath):
332
"""See Transport.rmdir."""
335
path = self._abspath(relpath)
337
except (IOError, OSError),e:
338
self._translate_error(e, path)
340
def _can_roundtrip_unix_modebits(self):
341
if sys.platform == 'win32':
348
class LocalRelpathServer(Server):
349
"""A pretend server for local transports, using relpaths."""
352
"""See Transport.Server.get_url."""
356
class LocalAbspathServer(Server):
357
"""A pretend server for local transports, using absolute paths."""
360
"""See Transport.Server.get_url."""
361
return os.path.abspath("")
364
class LocalURLServer(Server):
365
"""A pretend server for local transports, using file:// urls."""
368
"""See Transport.Server.get_url."""
369
return urlutils.local_path_to_url('')
372
def get_test_permutations():
373
"""Return the permutations to be used in testing."""
374
return [(LocalTransport, LocalRelpathServer),
375
(LocalTransport, LocalAbspathServer),
376
(LocalTransport, LocalURLServer),