~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Robert Collins
  • Date: 2005-10-11 07:00:25 UTC
  • mto: This revision was merged to the branch mainline in revision 1443.
  • Revision ID: robertc@robertcollins.net-20051011070025-bac6b53cb6186dfd
create a config module - there is enough config logic to make this worthwhile, and start testing config processing.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
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.
12
 
#
 
12
 
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
 
 
17
 
"""Transport for the local filesystem.
18
 
 
19
 
This is a fairly thin wrapper on regular file IO.
 
16
"""Implementation of Transport for the local filesystem.
20
17
"""
21
18
 
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
 
 
 
19
from bzrlib.transport import Transport, register_transport, \
 
20
    TransportError, NoSuchFile, FileExists
 
21
import os, errno
 
22
 
 
23
class LocalTransportError(TransportError):
 
24
    pass
35
25
 
36
26
class LocalTransport(Transport):
37
27
    """This is the transport agent for local filesystem access."""
38
28
 
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.",
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)
 
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)))
55
37
 
56
38
    def should_cache(self):
57
39
        return False
66
48
        else:
67
49
            return LocalTransport(self.abspath(offset))
68
50
 
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
 
 
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)
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.
 
52
        """Return the full url to the given relative path.
 
53
        This can be supplied with a string or a list
99
54
        """
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):
 
56
            relpath = [relpath]
 
57
        return os.path.join(self.base, *relpath)
103
58
 
104
59
    def relpath(self, abspath):
105
60
        """Return the local path portion from a given absolute path.
106
61
        """
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))
 
62
        from bzrlib.branch import _relpath
 
63
        return _relpath(self.base, abspath)
113
64
 
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)
116
67
 
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
121
72
        """
122
73
        try:
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)
127
 
 
128
 
    def put(self, relpath, f, mode=None):
 
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):
129
106
        """Copy the file-like or string object into the location.
130
107
 
131
108
        :param relpath: Location to put the contents, relative to base.
133
110
        """
134
111
        from bzrlib.atomicfile import AtomicFile
135
112
 
136
 
        path = relpath
137
113
        try:
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')
 
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)
143
120
        try:
144
121
            self._pump(f, fp)
145
122
            fp.commit()
146
123
        finally:
147
124
            fp.close()
148
125
 
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):
 
126
    def mkdir(self, relpath):
162
127
        """Create a directory at the given path."""
163
 
        path = relpath
164
128
        try:
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)
 
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)
171
136
 
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
 
        fp = None
176
 
        try:
177
 
            try:
178
 
                fp = open(abspath, 'ab')
179
 
                # FIXME should we really be chmodding every time ? RBC 20060523
180
 
                if mode is not None:
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)
185
 
            fp.seek(0, 2)
186
 
            result = fp.tell()
187
 
            self._pump(f, fp)
188
 
        finally:
189
 
            if fp is not None:
190
 
                fp.close()
191
 
        return result
 
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')
 
142
        self._pump(f, fp)
192
143
 
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)
 
146
        import shutil
 
147
        path_from = self.abspath(rel_from)
 
148
        path_to = self.abspath(rel_to)
197
149
        try:
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)
202
 
 
203
 
    def rename(self, rel_from, rel_to):
204
 
        path_from = self._abspath(rel_from)
205
 
        try:
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)
 
151
        except OSError,e:
 
152
            raise LocalTransportError(orig_error=e)
212
153
 
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)
217
158
 
218
159
        try:
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)
 
161
        except OSError,e:
 
162
            raise LocalTransportError(orig_error=e)
224
163
 
225
164
    def delete(self, relpath):
226
165
        """Delete the item at relpath"""
227
 
        path = relpath
228
166
        try:
229
 
            path = self._abspath(relpath)
230
 
            os.remove(path)
231
 
        except (IOError, OSError),e:
232
 
            self._translate_error(e, path)
 
167
            os.remove(self.abspath(relpath))
 
168
        except OSError,e:
 
169
            raise LocalTransportError(orig_error=e)
233
170
 
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.
236
173
 
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 :(
 
180
            import shutil
 
181
 
243
182
            total = self._get_total(relpaths)
244
183
            count = 0
245
184
            for path in relpaths:
246
185
                self._update_pb(pb, 'copy-to', count, total)
247
 
                try:
248
 
                    mypath = self._abspath(path)
249
 
                    otherpath = other._abspath(path)
250
 
                    shutil.copy(mypath, otherpath)
251
 
                    if mode is not None:
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))
255
187
                count += 1
256
188
            return count
257
189
        else:
258
 
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
 
190
            return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
259
191
 
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.
268
200
        """
269
 
        path = self._abspath(relpath)
270
201
        try:
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))
 
203
        except OSError,e:
 
204
            raise LocalTransportError(orig_error=e)
274
205
 
275
206
    def stat(self, relpath):
276
207
        """Return the stat information for a file.
277
208
        """
278
 
        path = relpath
279
209
        try:
280
 
            path = self._abspath(relpath)
281
 
            return os.stat(path)
282
 
        except (IOError, OSError),e:
283
 
            self._translate_error(e, path)
 
210
            return os.stat(self.abspath(relpath))
 
211
        except OSError,e:
 
212
            raise LocalTransportError(orig_error=e)
284
213
 
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()
288
217
        """
289
218
        from bzrlib.lock import ReadLock
290
 
        path = relpath
291
 
        try:
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))
296
220
 
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()
302
226
        """
303
227
        from bzrlib.lock import WriteLock
304
 
        return WriteLock(self._abspath(relpath))
305
 
 
306
 
    def rmdir(self, relpath):
307
 
        """See Transport.rmdir."""
308
 
        path = relpath
309
 
        try:
310
 
            path = self._abspath(relpath)
311
 
            os.rmdir(path)
312
 
        except (IOError, OSError),e:
313
 
            self._translate_error(e, path)
314
 
 
315
 
    def _can_roundtrip_unix_modebits(self):
316
 
        if sys.platform == 'win32':
317
 
            # anyone else?
318
 
            return False
319
 
        else:
320
 
            return True
321
 
 
322
 
 
323
 
class LocalRelpathServer(Server):
324
 
    """A pretend server for local transports, using relpaths."""
325
 
 
326
 
    def get_url(self):
327
 
        """See Transport.Server.get_url."""
328
 
        return "."
329
 
 
330
 
 
331
 
class LocalAbspathServer(Server):
332
 
    """A pretend server for local transports, using absolute paths."""
333
 
 
334
 
    def get_url(self):
335
 
        """See Transport.Server.get_url."""
336
 
        return os.path.abspath("")
337
 
 
338
 
 
339
 
class LocalURLServer(Server):
340
 
    """A pretend server for local transports, using file:// urls."""
341
 
 
342
 
    def get_url(self):
343
 
        """See Transport.Server.get_url."""
344
 
        return urlutils.local_path_to_url('')
345
 
 
346
 
 
347
 
def get_test_permutations():
348
 
    """Return the permutations to be used in testing."""
349
 
    return [(LocalTransport, LocalRelpathServer),
350
 
            (LocalTransport, LocalAbspathServer),
351
 
            (LocalTransport, LocalURLServer),
352
 
            ]
 
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)