~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Robert Collins
  • Date: 2006-06-26 16:23:10 UTC
  • mfrom: (1780.2.1 misc-fixen)
  • mto: This revision was merged to the branch mainline in revision 1815.
  • Revision ID: robertc@robertcollins.net-20060626162310-98f5b55b8cc19d46
(robertc) Misc minor typos and the like.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
 
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
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.
 
16
 
 
17
"""Transport for the local filesystem.
 
18
 
 
19
This is a fairly thin wrapper on regular file IO.
17
20
"""
18
21
 
19
 
from bzrlib.transport import Transport, register_transport, \
20
 
    TransportError, NoSuchFile, FileExists
21
 
import os, errno
22
 
 
23
 
class LocalTransportError(TransportError):
24
 
    pass
 
22
import os
 
23
import shutil
 
24
import sys
 
25
from stat import ST_MODE, S_ISDIR, ST_SIZE
 
26
import tempfile
 
27
 
 
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
 
34
 
25
35
 
26
36
class LocalTransport(Transport):
27
37
    """This is the transport agent for local filesystem access."""
28
38
 
29
39
    def __init__(self, base):
30
40
        """Set the base path where files will be stored."""
31
 
        if base.startswith('file://'):
32
 
            base = base[7:]
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.",
 
46
                 DeprecationWarning,
 
47
                 stacklevel=2
 
48
                 )
 
49
            base = urlutils.local_path_to_url(base)
 
50
        if base[-1] != '/':
 
51
            base = 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)
37
55
 
38
56
    def should_cache(self):
39
57
        return False
48
66
        else:
49
67
            return LocalTransport(self.abspath(offset))
50
68
 
 
69
    def _abspath(self, relative_reference):
 
70
        """Return a path for use in os calls.
 
71
 
 
72
        Several assumptions are made:
 
73
         - relative_reference does not contain '..'
 
74
         - relative_reference is url escaped.
 
75
        """
 
76
        if relative_reference in ('.', ''):
 
77
            return self._local_base
 
78
        return self._local_base + urlutils.unescape(relative_reference)
 
79
 
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)
 
88
 
 
89
    def local_abspath(self, relpath):
 
90
        """Transform the given relative path URL into the actual path on disk
 
91
 
 
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.
 
96
        
 
97
        This function is quite expensive: it calls realpath which resolves
 
98
        symlinks.
54
99
        """
55
 
        if isinstance(relpath, basestring):
56
 
            relpath = [relpath]
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)
58
103
 
59
104
    def relpath(self, abspath):
60
105
        """Return the local path portion from a given absolute path.
61
106
        """
62
 
        from bzrlib.branch import _relpath
63
 
        return _relpath(self.base, abspath)
 
107
        if abspath is None:
 
108
            abspath = u'.'
 
109
 
 
110
        return urlutils.file_relpath(
 
111
            urlutils.strip_trailing_slash(self.base), 
 
112
            urlutils.strip_trailing_slash(abspath))
64
113
 
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)
67
116
 
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
72
121
        """
73
122
        try:
74
 
            path = self.abspath(relpath)
 
123
            path = self._abspath(relpath)
75
124
            return open(path, 'rb')
76
 
        except IOError,e:
77
 
            if e.errno == errno.ENOENT:
78
 
                raise NoSuchFile('File %r does not exist' % path, orig_error=e)
79
 
            raise LocalTransportError(orig_error=e)
80
 
 
81
 
    def get_partial(self, relpath, start, length=None):
82
 
        """Get just part of a file.
83
 
 
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.
91
 
        """
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.
95
 
        try:
96
 
            path = self.abspath(relpath)
97
 
            f = open(path, 'rb')
98
 
            f.seek(start, 0)
99
 
            return f
100
 
        except IOError,e:
101
 
            if e.errno == errno.ENOENT:
102
 
                raise NoSuchFile('File %r does not exist' % path, orig_error=e)
103
 
            raise LocalTransportError(orig_error=e)
104
 
 
105
 
    def put(self, relpath, f):
 
125
        except (IOError, OSError),e:
 
126
            self._translate_error(e, path)
 
127
 
 
128
    def put(self, relpath, f, mode=None):
106
129
        """Copy the file-like or string object into the location.
107
130
 
108
131
        :param relpath: Location to put the contents, relative to base.
110
133
        """
111
134
        from bzrlib.atomicfile import AtomicFile
112
135
 
 
136
        path = relpath
113
137
        try:
114
 
            path = self.abspath(relpath)
115
 
            fp = AtomicFile(path, 'wb')
116
 
        except IOError, e:
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)
120
143
        try:
121
144
            self._pump(f, fp)
122
145
            fp.commit()
123
146
        finally:
124
147
            fp.close()
125
148
 
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'.'))
 
152
        while queue:
 
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)
 
158
            else:
 
159
                yield relpath
 
160
 
 
161
    def mkdir(self, relpath, mode=None):
127
162
        """Create a directory at the given path."""
 
163
        path = relpath
128
164
        try:
129
 
            os.mkdir(self.abspath(relpath))
130
 
        except OSError,e:
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)
 
166
            os.mkdir(path)
 
167
            if mode is not None:
 
168
                os.chmod(path, mode)
 
169
        except (IOError, OSError),e:
 
170
            self._translate_error(e, path)
136
171
 
137
 
    def append(self, relpath, f):
138
 
        """Append the text in the file-like object into the final
139
 
        location.
140
 
        """
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)
 
175
        try:
 
176
            fp = open(abspath, 'ab')
 
177
            # FIXME should we really be chmodding every time ? RBC 20060523
 
178
            if mode is not None:
 
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)
 
183
        fp.seek(0, 2)
 
184
        result = fp.tell()
142
185
        self._pump(f, fp)
 
186
        return result
143
187
 
144
188
    def copy(self, rel_from, rel_to):
145
189
        """Copy the item at rel_from to the location at rel_to"""
146
 
        import shutil
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)
149
192
        try:
150
193
            shutil.copy(path_from, path_to)
151
 
        except OSError,e:
152
 
            raise LocalTransportError(orig_error=e)
 
194
        except (IOError, OSError),e:
 
195
            # TODO: What about path_to?
 
196
            self._translate_error(e, path_from)
 
197
 
 
198
    def rename(self, rel_from, rel_to):
 
199
        path_from = self._abspath(rel_from)
 
200
        try:
 
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)
153
207
 
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)
158
212
 
159
213
        try:
160
 
            os.rename(path_from, path_to)
161
 
        except OSError,e:
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)
163
219
 
164
220
    def delete(self, relpath):
165
221
        """Delete the item at relpath"""
 
222
        path = relpath
166
223
        try:
167
 
            os.remove(self.abspath(relpath))
168
 
        except OSError,e:
169
 
            raise LocalTransportError(orig_error=e)
 
224
            path = self._abspath(relpath)
 
225
            os.remove(path)
 
226
        except (IOError, OSError),e:
 
227
            self._translate_error(e, path)
170
228
 
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.
173
231
 
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 :(
180
 
            import shutil
181
 
 
182
238
            total = self._get_total(relpaths)
183
239
            count = 0
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))
 
242
                try:
 
243
                    mypath = self._abspath(path)
 
244
                    otherpath = other._abspath(path)
 
245
                    shutil.copy(mypath, otherpath)
 
246
                    if mode is not None:
 
247
                        os.chmod(otherpath, mode)
 
248
                except (IOError, OSError),e:
 
249
                    self._translate_error(e, path)
187
250
                count += 1
188
251
            return count
189
252
        else:
190
 
            return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
 
253
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
191
254
 
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.
200
263
        """
 
264
        path = self._abspath(relpath)
201
265
        try:
202
 
            return os.listdir(self.abspath(relpath))
203
 
        except OSError,e:
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)
205
269
 
206
270
    def stat(self, relpath):
207
271
        """Return the stat information for a file.
208
272
        """
 
273
        path = relpath
209
274
        try:
210
 
            return os.stat(self.abspath(relpath))
211
 
        except OSError,e:
212
 
            raise LocalTransportError(orig_error=e)
 
275
            path = self._abspath(relpath)
 
276
            return os.stat(path)
 
277
        except (IOError, OSError),e:
 
278
            self._translate_error(e, path)
213
279
 
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()
217
283
        """
218
284
        from bzrlib.lock import ReadLock
219
 
        return ReadLock(self.abspath(relpath))
 
285
        path = relpath
 
286
        try:
 
287
            path = self._abspath(relpath)
 
288
            return ReadLock(path)
 
289
        except (IOError, OSError), e:
 
290
            self._translate_error(e, path)
220
291
 
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()
226
297
        """
227
298
        from bzrlib.lock import WriteLock
228
 
        return WriteLock(self.abspath(relpath))
229
 
 
230
 
# If nothing else matches, try the LocalTransport
231
 
register_transport(None, LocalTransport)
232
 
register_transport('file://', LocalTransport)
 
299
        return WriteLock(self._abspath(relpath))
 
300
 
 
301
    def rmdir(self, relpath):
 
302
        """See Transport.rmdir."""
 
303
        path = relpath
 
304
        try:
 
305
            path = self._abspath(relpath)
 
306
            os.rmdir(path)
 
307
        except (IOError, OSError),e:
 
308
            self._translate_error(e, path)
 
309
 
 
310
    def _can_roundtrip_unix_modebits(self):
 
311
        if sys.platform == 'win32':
 
312
            # anyone else?
 
313
            return False
 
314
        else:
 
315
            return True
 
316
 
 
317
 
 
318
class LocalRelpathServer(Server):
 
319
    """A pretend server for local transports, using relpaths."""
 
320
 
 
321
    def get_url(self):
 
322
        """See Transport.Server.get_url."""
 
323
        return "."
 
324
 
 
325
 
 
326
class LocalAbspathServer(Server):
 
327
    """A pretend server for local transports, using absolute paths."""
 
328
 
 
329
    def get_url(self):
 
330
        """See Transport.Server.get_url."""
 
331
        return os.path.abspath("")
 
332
 
 
333
 
 
334
class LocalURLServer(Server):
 
335
    """A pretend server for local transports, using file:// urls."""
 
336
 
 
337
    def get_url(self):
 
338
        """See Transport.Server.get_url."""
 
339
        return urlutils.local_path_to_url('')
 
340
 
 
341
 
 
342
def get_test_permutations():
 
343
    """Return the permutations to be used in testing."""
 
344
    return [(LocalTransport, LocalRelpathServer),
 
345
            (LocalTransport, LocalAbspathServer),
 
346
            (LocalTransport, LocalURLServer),
 
347
            ]