~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

Basic BzrDir support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
This is a fairly thin wrapper on regular file IO."""
20
20
 
21
21
import os
22
 
import errno
23
22
import shutil
24
23
from stat import ST_MODE, S_ISDIR, ST_SIZE
25
24
import tempfile
26
25
import urllib
27
26
 
28
27
from bzrlib.trace import mutter
29
 
from bzrlib.transport import Transport, register_transport, \
30
 
    TransportError, NoSuchFile, FileExists
31
 
from bzrlib.osutils import abspath
32
 
 
33
 
class LocalTransportError(TransportError):
34
 
    pass
 
28
from bzrlib.transport import Transport, Server
 
29
from bzrlib.osutils import abspath, realpath, normpath, pathjoin, rename
35
30
 
36
31
 
37
32
class LocalTransport(Transport):
43
38
            base = base[7:]
44
39
        # realpath is incompatible with symlinks. When we traverse
45
40
        # up we might be able to normpath stuff. RBC 20051003
46
 
        super(LocalTransport, self).__init__(
47
 
            os.path.normpath(abspath(base)))
 
41
        base = normpath(abspath(base))
 
42
        if base[-1] != '/':
 
43
            base = base + '/'
 
44
        super(LocalTransport, self).__init__(base)
48
45
 
49
46
    def should_cache(self):
50
47
        return False
63
60
        """Return the full url to the given relative URL.
64
61
        This can be supplied with a string or a list
65
62
        """
66
 
        assert isinstance(relpath, basestring)
67
 
        return os.path.join(self.base, urllib.unquote(relpath))
 
63
        assert isinstance(relpath, basestring), (type(relpath), relpath)
 
64
        return pathjoin(self.base, urllib.unquote(relpath))
68
65
 
69
66
    def relpath(self, abspath):
70
67
        """Return the local path portion from a given absolute path.
71
68
        """
72
69
        from bzrlib.osutils import relpath
73
70
        if abspath is None:
74
 
            abspath = '.'
75
 
        return relpath(self.base, abspath)
 
71
            abspath = u'.'
 
72
        if abspath.endswith('/'):
 
73
            abspath = abspath[:-1]
 
74
        return relpath(self.base[:-1], abspath)
76
75
 
77
76
    def has(self, relpath):
78
77
        return os.access(self.abspath(relpath), os.F_OK)
85
84
        try:
86
85
            path = self.abspath(relpath)
87
86
            return open(path, 'rb')
88
 
        except IOError,e:
89
 
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
90
 
                raise NoSuchFile('File or directory %r does not exist' % path, orig_error=e)
91
 
            raise LocalTransportError(orig_error=e)
 
87
        except (IOError, OSError),e:
 
88
            self._translate_error(e, path)
92
89
 
93
 
    def put(self, relpath, f):
 
90
    def put(self, relpath, f, mode=None):
94
91
        """Copy the file-like or string object into the location.
95
92
 
96
93
        :param relpath: Location to put the contents, relative to base.
98
95
        """
99
96
        from bzrlib.atomicfile import AtomicFile
100
97
 
 
98
        path = relpath
101
99
        try:
102
100
            path = self.abspath(relpath)
103
 
            fp = AtomicFile(path, 'wb')
104
 
        except IOError, e:
105
 
            if e.errno == errno.ENOENT:
106
 
                raise NoSuchFile('File %r does not exist' % path, orig_error=e)
107
 
            raise LocalTransportError(orig_error=e)
 
101
            fp = AtomicFile(path, 'wb', new_mode=mode)
 
102
        except (IOError, OSError),e:
 
103
            self._translate_error(e, path)
108
104
        try:
109
105
            self._pump(f, fp)
110
106
            fp.commit()
113
109
 
114
110
    def iter_files_recursive(self):
115
111
        """Iter the relative paths of files in the transports sub-tree."""
116
 
        queue = list(self.list_dir('.'))
 
112
        queue = list(self.list_dir(u'.'))
117
113
        while queue:
118
 
            relpath = queue.pop(0)
 
114
            relpath = urllib.quote(queue.pop(0))
119
115
            st = self.stat(relpath)
120
116
            if S_ISDIR(st[ST_MODE]):
121
117
                for i, basename in enumerate(self.list_dir(relpath)):
123
119
            else:
124
120
                yield relpath
125
121
 
126
 
    def mkdir(self, relpath):
 
122
    def mkdir(self, relpath, mode=None):
127
123
        """Create a directory at the given path."""
 
124
        path = relpath
128
125
        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)
 
126
            path = self.abspath(relpath)
 
127
            os.mkdir(path)
 
128
            if mode is not None:
 
129
                os.chmod(path, mode)
 
130
        except (IOError, OSError),e:
 
131
            self._translate_error(e, path)
136
132
 
137
133
    def append(self, relpath, f):
138
134
        """Append the text in the file-like object into the final
139
135
        location.
140
136
        """
141
 
        fp = open(self.abspath(relpath), 'ab')
 
137
        try:
 
138
            fp = open(self.abspath(relpath), 'ab')
 
139
        except (IOError, OSError),e:
 
140
            self._translate_error(e, relpath)
142
141
        self._pump(f, fp)
143
142
 
144
143
    def copy(self, rel_from, rel_to):
148
147
        path_to = self.abspath(rel_to)
149
148
        try:
150
149
            shutil.copy(path_from, path_to)
151
 
        except OSError,e:
152
 
            raise LocalTransportError(orig_error=e)
 
150
        except (IOError, OSError),e:
 
151
            # TODO: What about path_to?
 
152
            self._translate_error(e, path_from)
153
153
 
154
154
    def move(self, rel_from, rel_to):
155
155
        """Move the item at rel_from to the location at rel_to"""
157
157
        path_to = self.abspath(rel_to)
158
158
 
159
159
        try:
160
 
            os.rename(path_from, path_to)
161
 
        except OSError,e:
162
 
            raise LocalTransportError(orig_error=e)
 
160
            rename(path_from, path_to)
 
161
        except (IOError, OSError),e:
 
162
            # TODO: What about path_to?
 
163
            self._translate_error(e, path_from)
163
164
 
164
165
    def delete(self, relpath):
165
166
        """Delete the item at relpath"""
 
167
        path = relpath
166
168
        try:
167
 
            os.remove(self.abspath(relpath))
168
 
        except OSError,e:
169
 
            raise LocalTransportError(orig_error=e)
 
169
            path = self.abspath(relpath)
 
170
            os.remove(path)
 
171
        except (IOError, OSError),e:
 
172
            # TODO: What about path_to?
 
173
            self._translate_error(e, path)
170
174
 
171
 
    def copy_to(self, relpaths, other, pb=None):
 
175
    def copy_to(self, relpaths, other, mode=None, pb=None):
172
176
        """Copy a set of entries from self into another Transport.
173
177
 
174
178
        :param relpaths: A list/generator of entries to be copied.
183
187
            count = 0
184
188
            for path in relpaths:
185
189
                self._update_pb(pb, 'copy-to', count, total)
186
 
                shutil.copy(self.abspath(path), other.abspath(path))
 
190
                try:
 
191
                    mypath = self.abspath(path)
 
192
                    otherpath = other.abspath(path)
 
193
                    shutil.copy(mypath, otherpath)
 
194
                    if mode is not None:
 
195
                        os.chmod(otherpath, mode)
 
196
                except (IOError, OSError),e:
 
197
                    self._translate_error(e, path)
187
198
                count += 1
188
199
            return count
189
200
        else:
190
 
            return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
 
201
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
191
202
 
192
203
    def listable(self):
193
204
        """See Transport.listable."""
198
209
        WARNING: many transports do not support this, so trying avoid using
199
210
        it if at all possible.
200
211
        """
 
212
        path = relpath
201
213
        try:
202
 
            return os.listdir(self.abspath(relpath))
203
 
        except OSError,e:
204
 
            raise LocalTransportError(orig_error=e)
 
214
            path = self.abspath(relpath)
 
215
            return os.listdir(path)
 
216
        except (IOError, OSError),e:
 
217
            self._translate_error(e, path)
205
218
 
206
219
    def stat(self, relpath):
207
220
        """Return the stat information for a file.
208
221
        """
 
222
        path = relpath
209
223
        try:
210
 
            return os.stat(self.abspath(relpath))
211
 
        except OSError,e:
212
 
            raise LocalTransportError(orig_error=e)
 
224
            path = self.abspath(relpath)
 
225
            return os.stat(path)
 
226
        except (IOError, OSError),e:
 
227
            self._translate_error(e, path)
213
228
 
214
229
    def lock_read(self, relpath):
215
230
        """Lock the given file for shared (read) access.
216
231
        :return: A lock object, which should be passed to Transport.unlock()
217
232
        """
218
233
        from bzrlib.lock import ReadLock
219
 
        return ReadLock(self.abspath(relpath))
 
234
        path = relpath
 
235
        try:
 
236
            path = self.abspath(relpath)
 
237
            return ReadLock(path)
 
238
        except (IOError, OSError), e:
 
239
            self._translate_error(e, path)
220
240
 
221
241
    def lock_write(self, relpath):
222
242
        """Lock the given file for exclusive (write) access.
227
247
        from bzrlib.lock import WriteLock
228
248
        return WriteLock(self.abspath(relpath))
229
249
 
 
250
    def rmdir(self, relpath):
 
251
        """See Transport.rmdir."""
 
252
        path = relpath
 
253
        try:
 
254
            path = self.abspath(relpath)
 
255
            os.rmdir(path)
 
256
        except (IOError, OSError),e:
 
257
            self._translate_error(e, path)
230
258
 
231
259
class ScratchTransport(LocalTransport):
232
260
    """A transport that works in a temporary dir and cleans up after itself.
243
271
    def __del__(self):
244
272
        shutil.rmtree(self.base, ignore_errors=True)
245
273
        mutter("%r destroyed" % self)
 
274
 
 
275
 
 
276
class LocalRelpathServer(Server):
 
277
    """A pretend server for local transports, using relpaths."""
 
278
 
 
279
    def get_url(self):
 
280
        """See Transport.Server.get_url."""
 
281
        return "."
 
282
 
 
283
 
 
284
class LocalAbspathServer(Server):
 
285
    """A pretend server for local transports, using absolute paths."""
 
286
 
 
287
    def get_url(self):
 
288
        """See Transport.Server.get_url."""
 
289
        return os.path.abspath("")
 
290
 
 
291
 
 
292
class LocalURLServer(Server):
 
293
    """A pretend server for local transports, using file:// urls."""
 
294
 
 
295
    def get_url(self):
 
296
        """See Transport.Server.get_url."""
 
297
        # FIXME: \ to / on windows
 
298
        return "file://%s" % os.path.abspath("")
 
299
 
 
300
 
 
301
def get_test_permutations():
 
302
    """Return the permutations to be used in testing."""
 
303
    return [(LocalTransport, LocalRelpathServer),
 
304
            (LocalTransport, LocalAbspathServer),
 
305
            (LocalTransport, LocalURLServer),
 
306
            ]