~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Olaf Conradi
  • Date: 2006-03-28 23:30:02 UTC
  • mto: (1661.1.1 bzr.mbp.remember)
  • mto: This revision was merged to the branch mainline in revision 1663.
  • Revision ID: olaf@conradi.org-20060328233002-f6262df0e19c1963
Added testcases for using pull with --remember. Moved remember code to
beginning of cmd_pull. This remembers the location in case of a failure
during pull.

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
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):
40
35
    def __init__(self, base):
41
36
        """Set the base path where files will be stored."""
42
37
        if base.startswith('file://'):
43
 
            base = base[7:]
 
38
            base = base[len('file://'):]
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
114
            relpath = queue.pop(0)
119
115
            st = self.stat(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)
 
141
        result = fp.tell()
142
142
        self._pump(f, fp)
 
143
        return result
143
144
 
144
145
    def copy(self, rel_from, rel_to):
145
146
        """Copy the item at rel_from to the location at rel_to"""
148
149
        path_to = self.abspath(rel_to)
149
150
        try:
150
151
            shutil.copy(path_from, path_to)
151
 
        except OSError,e:
152
 
            raise LocalTransportError(orig_error=e)
 
152
        except (IOError, OSError),e:
 
153
            # TODO: What about path_to?
 
154
            self._translate_error(e, path_from)
 
155
 
 
156
    def rename(self, rel_from, rel_to):
 
157
        path_from = self.abspath(rel_from)
 
158
        try:
 
159
            # *don't* call bzrlib.osutils.rename, because we want to 
 
160
            # detect errors on rename
 
161
            os.rename(path_from, self.abspath(rel_to))
 
162
        except (IOError, OSError),e:
 
163
            # TODO: What about path_to?
 
164
            self._translate_error(e, path_from)
153
165
 
154
166
    def move(self, rel_from, rel_to):
155
167
        """Move the item at rel_from to the location at rel_to"""
157
169
        path_to = self.abspath(rel_to)
158
170
 
159
171
        try:
160
 
            os.rename(path_from, path_to)
161
 
        except OSError,e:
162
 
            raise LocalTransportError(orig_error=e)
 
172
            # this version will delete the destination if necessary
 
173
            rename(path_from, path_to)
 
174
        except (IOError, OSError),e:
 
175
            # TODO: What about path_to?
 
176
            self._translate_error(e, path_from)
163
177
 
164
178
    def delete(self, relpath):
165
179
        """Delete the item at relpath"""
 
180
        path = relpath
166
181
        try:
167
 
            os.remove(self.abspath(relpath))
168
 
        except OSError,e:
169
 
            raise LocalTransportError(orig_error=e)
 
182
            path = self.abspath(relpath)
 
183
            os.remove(path)
 
184
        except (IOError, OSError),e:
 
185
            # TODO: What about path_to?
 
186
            self._translate_error(e, path)
170
187
 
171
 
    def copy_to(self, relpaths, other, pb=None):
 
188
    def copy_to(self, relpaths, other, mode=None, pb=None):
172
189
        """Copy a set of entries from self into another Transport.
173
190
 
174
191
        :param relpaths: A list/generator of entries to be copied.
183
200
            count = 0
184
201
            for path in relpaths:
185
202
                self._update_pb(pb, 'copy-to', count, total)
186
 
                shutil.copy(self.abspath(path), other.abspath(path))
 
203
                try:
 
204
                    mypath = self.abspath(path)
 
205
                    otherpath = other.abspath(path)
 
206
                    shutil.copy(mypath, otherpath)
 
207
                    if mode is not None:
 
208
                        os.chmod(otherpath, mode)
 
209
                except (IOError, OSError),e:
 
210
                    self._translate_error(e, path)
187
211
                count += 1
188
212
            return count
189
213
        else:
190
 
            return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
 
214
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
191
215
 
192
216
    def listable(self):
193
217
        """See Transport.listable."""
198
222
        WARNING: many transports do not support this, so trying avoid using
199
223
        it if at all possible.
200
224
        """
 
225
        path = self.abspath(relpath)
201
226
        try:
202
 
            return os.listdir(self.abspath(relpath))
203
 
        except OSError,e:
204
 
            raise LocalTransportError(orig_error=e)
 
227
            return [urllib.quote(entry) for entry in os.listdir(path)]
 
228
        except (IOError, OSError),e:
 
229
            self._translate_error(e, path)
205
230
 
206
231
    def stat(self, relpath):
207
232
        """Return the stat information for a file.
208
233
        """
 
234
        path = relpath
209
235
        try:
210
 
            return os.stat(self.abspath(relpath))
211
 
        except OSError,e:
212
 
            raise LocalTransportError(orig_error=e)
 
236
            path = self.abspath(relpath)
 
237
            return os.stat(path)
 
238
        except (IOError, OSError),e:
 
239
            self._translate_error(e, path)
213
240
 
214
241
    def lock_read(self, relpath):
215
242
        """Lock the given file for shared (read) access.
216
243
        :return: A lock object, which should be passed to Transport.unlock()
217
244
        """
218
245
        from bzrlib.lock import ReadLock
219
 
        return ReadLock(self.abspath(relpath))
 
246
        path = relpath
 
247
        try:
 
248
            path = self.abspath(relpath)
 
249
            return ReadLock(path)
 
250
        except (IOError, OSError), e:
 
251
            self._translate_error(e, path)
220
252
 
221
253
    def lock_write(self, relpath):
222
254
        """Lock the given file for exclusive (write) access.
227
259
        from bzrlib.lock import WriteLock
228
260
        return WriteLock(self.abspath(relpath))
229
261
 
 
262
    def rmdir(self, relpath):
 
263
        """See Transport.rmdir."""
 
264
        path = relpath
 
265
        try:
 
266
            path = self.abspath(relpath)
 
267
            os.rmdir(path)
 
268
        except (IOError, OSError),e:
 
269
            self._translate_error(e, path)
 
270
 
230
271
 
231
272
class ScratchTransport(LocalTransport):
232
273
    """A transport that works in a temporary dir and cleans up after itself.
243
284
    def __del__(self):
244
285
        shutil.rmtree(self.base, ignore_errors=True)
245
286
        mutter("%r destroyed" % self)
 
287
 
 
288
 
 
289
class LocalRelpathServer(Server):
 
290
    """A pretend server for local transports, using relpaths."""
 
291
 
 
292
    def get_url(self):
 
293
        """See Transport.Server.get_url."""
 
294
        return "."
 
295
 
 
296
 
 
297
class LocalAbspathServer(Server):
 
298
    """A pretend server for local transports, using absolute paths."""
 
299
 
 
300
    def get_url(self):
 
301
        """See Transport.Server.get_url."""
 
302
        return os.path.abspath("")
 
303
 
 
304
 
 
305
class LocalURLServer(Server):
 
306
    """A pretend server for local transports, using file:// urls."""
 
307
 
 
308
    def get_url(self):
 
309
        """See Transport.Server.get_url."""
 
310
        # FIXME: \ to / on windows
 
311
        return "file://%s" % os.path.abspath("")
 
312
 
 
313
 
 
314
def get_test_permutations():
 
315
    """Return the permutations to be used in testing."""
 
316
    return [(LocalTransport, LocalRelpathServer),
 
317
            (LocalTransport, LocalAbspathServer),
 
318
            (LocalTransport, LocalURLServer),
 
319
            ]