~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Aaron Bentley
  • Date: 2006-05-30 15:53:33 UTC
  • mto: This revision was merged to the branch mainline in revision 1738.
  • Revision ID: abentley@panoramicfeedback.com-20060530155333-2fd164d1cdf2afc3
Move BadBundle error (and subclasses) to errors.py

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
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
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
16
 
17
17
"""Transport for the local filesystem.
18
18
 
19
 
This is a fairly thin wrapper on regular file IO.
20
 
"""
 
19
This is a fairly thin wrapper on regular file IO."""
21
20
 
22
21
import os
23
22
import shutil
24
23
import sys
25
 
from stat import ST_MODE, S_ISDIR, ST_SIZE, S_IMODE
 
24
from stat import ST_MODE, S_ISDIR, ST_SIZE
26
25
import tempfile
 
26
import urllib
27
27
 
28
 
from bzrlib import (
29
 
    osutils,
30
 
    urlutils,
31
 
    )
32
 
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename,
33
 
                            check_legal_path, rmtree)
34
 
from bzrlib.symbol_versioning import warn
35
28
from bzrlib.trace import mutter
36
29
from bzrlib.transport import Transport, Server
37
 
 
38
 
 
39
 
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY
 
30
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename, 
 
31
                            check_legal_path, rmtree)
40
32
 
41
33
 
42
34
class LocalTransport(Transport):
44
36
 
45
37
    def __init__(self, base):
46
38
        """Set the base path where files will be stored."""
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.",
52
 
                 DeprecationWarning,
53
 
                 stacklevel=2
54
 
                 )
55
 
            base = urlutils.local_path_to_url(base)
 
39
        if base.startswith('file://'):
 
40
            base = base[len('file://'):]
 
41
        # realpath is incompatible with symlinks. When we traverse
 
42
        # up we might be able to normpath stuff. RBC 20051003
 
43
        base = normpath(abspath(base))
56
44
        if base[-1] != '/':
57
45
            base = base + '/'
58
46
        super(LocalTransport, self).__init__(base)
59
 
        self._local_base = urlutils.local_path_from_url(base)
60
47
 
61
48
    def should_cache(self):
62
49
        return False
71
58
        else:
72
59
            return LocalTransport(self.abspath(offset))
73
60
 
74
 
    def _abspath(self, relative_reference):
75
 
        """Return a path for use in os calls.
76
 
 
77
 
        Several assumptions are made:
78
 
         - relative_reference does not contain '..'
79
 
         - relative_reference is url escaped.
80
 
        """
81
 
        if relative_reference in ('.', ''):
82
 
            return self._local_base
83
 
        return self._local_base + urlutils.unescape(relative_reference)
84
 
 
85
61
    def abspath(self, relpath):
86
62
        """Return the full url to the given relative URL."""
87
 
        # TODO: url escape the result. RBC 20060523.
88
63
        assert isinstance(relpath, basestring), (type(relpath), 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)
93
 
 
94
 
    def local_abspath(self, relpath):
95
 
        """Transform the given relative path URL into the actual path on disk
96
 
 
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.
101
 
        
102
 
        This function is quite expensive: it calls realpath which resolves
103
 
        symlinks.
104
 
        """
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)
 
64
        result = normpath(pathjoin(self.base, urllib.unquote(relpath)))
 
65
        #if result[-1] != '/':
 
66
        #    result += '/'
 
67
        return result
108
68
 
109
69
    def relpath(self, abspath):
110
70
        """Return the local path portion from a given absolute path.
111
71
        """
 
72
        from bzrlib.osutils import relpath, strip_trailing_slash
112
73
        if abspath is None:
113
74
            abspath = u'.'
114
75
 
115
 
        return urlutils.file_relpath(
116
 
            urlutils.strip_trailing_slash(self.base), 
117
 
            urlutils.strip_trailing_slash(abspath))
 
76
        return relpath(strip_trailing_slash(self.base), 
 
77
                       strip_trailing_slash(abspath))
118
78
 
119
79
    def has(self, relpath):
120
 
        return os.access(self._abspath(relpath), os.F_OK)
 
80
        return os.access(self.abspath(relpath), os.F_OK)
121
81
 
122
82
    def get(self, relpath):
123
83
        """Get the file at the given relative path.
125
85
        :param relpath: The relative path to the file
126
86
        """
127
87
        try:
128
 
            path = self._abspath(relpath)
 
88
            path = self.abspath(relpath)
129
89
            return open(path, 'rb')
130
90
        except (IOError, OSError),e:
131
91
            self._translate_error(e, path)
140
100
 
141
101
        path = relpath
142
102
        try:
143
 
            path = self._abspath(relpath)
 
103
            path = self.abspath(relpath)
144
104
            check_legal_path(path)
145
105
            fp = AtomicFile(path, 'wb', new_mode=mode)
146
106
        except (IOError, OSError),e:
167
127
        """Create a directory at the given path."""
168
128
        path = relpath
169
129
        try:
170
 
            if mode is None:
171
 
                # os.mkdir() will filter through umask
172
 
                local_mode = 0777
173
 
            else:
174
 
                local_mode = mode
175
 
            path = self._abspath(relpath)
176
 
            os.mkdir(path, local_mode)
 
130
            path = self.abspath(relpath)
 
131
            os.mkdir(path)
177
132
            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
180
133
                os.chmod(path, mode)
181
134
        except (IOError, OSError),e:
182
135
            self._translate_error(e, path)
183
136
 
184
137
    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)
187
 
        if mode is None:
188
 
            # os.open() will automatically use the umask
189
 
            local_mode = 0666
190
 
        else:
191
 
            local_mode = mode
 
138
        """Append the text in the file-like object into the final
 
139
        location.
 
140
        """
192
141
        try:
193
 
            fd = os.open(abspath, _append_flags, local_mode)
 
142
            fp = open(self.abspath(relpath), 'ab')
 
143
            if mode is not None:
 
144
                os.chmod(self.abspath(relpath), mode)
194
145
        except (IOError, OSError),e:
195
146
            self._translate_error(e, relpath)
196
 
        try:
197
 
            st = os.fstat(fd)
198
 
            result = st.st_size
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)
204
 
        finally:
205
 
            os.close(fd)
 
147
        # win32 workaround (tell on an unwritten file returns 0)
 
148
        fp.seek(0, 2)
 
149
        result = fp.tell()
 
150
        self._pump(f, fp)
206
151
        return result
207
152
 
208
 
    def _pump_to_fd(self, fromfile, to_fd):
209
 
        """Copy contents of one file to another."""
210
 
        BUFSIZE = 32768
211
 
        while True:
212
 
            b = fromfile.read(BUFSIZE)
213
 
            if not b:
214
 
                break
215
 
            os.write(to_fd, b)
216
 
 
217
153
    def copy(self, rel_from, rel_to):
218
154
        """Copy the item at rel_from to the location at rel_to"""
219
 
        path_from = self._abspath(rel_from)
220
 
        path_to = self._abspath(rel_to)
 
155
        path_from = self.abspath(rel_from)
 
156
        path_to = self.abspath(rel_to)
221
157
        try:
222
158
            shutil.copy(path_from, path_to)
223
159
        except (IOError, OSError),e:
225
161
            self._translate_error(e, path_from)
226
162
 
227
163
    def rename(self, rel_from, rel_to):
228
 
        path_from = self._abspath(rel_from)
 
164
        path_from = self.abspath(rel_from)
229
165
        try:
230
166
            # *don't* call bzrlib.osutils.rename, because we want to 
231
167
            # detect errors on rename
232
 
            os.rename(path_from, self._abspath(rel_to))
 
168
            os.rename(path_from, self.abspath(rel_to))
233
169
        except (IOError, OSError),e:
234
170
            # TODO: What about path_to?
235
171
            self._translate_error(e, path_from)
236
172
 
237
173
    def move(self, rel_from, rel_to):
238
174
        """Move the item at rel_from to the location at rel_to"""
239
 
        path_from = self._abspath(rel_from)
240
 
        path_to = self._abspath(rel_to)
 
175
        path_from = self.abspath(rel_from)
 
176
        path_to = self.abspath(rel_to)
241
177
 
242
178
        try:
243
179
            # this version will delete the destination if necessary
250
186
        """Delete the item at relpath"""
251
187
        path = relpath
252
188
        try:
253
 
            path = self._abspath(relpath)
 
189
            path = self.abspath(relpath)
254
190
            os.remove(path)
255
191
        except (IOError, OSError),e:
 
192
            # TODO: What about path_to?
256
193
            self._translate_error(e, path)
257
194
 
258
195
    def copy_to(self, relpaths, other, mode=None, pb=None):
269
206
            for path in relpaths:
270
207
                self._update_pb(pb, 'copy-to', count, total)
271
208
                try:
272
 
                    mypath = self._abspath(path)
273
 
                    otherpath = other._abspath(path)
 
209
                    mypath = self.abspath(path)
 
210
                    otherpath = other.abspath(path)
274
211
                    shutil.copy(mypath, otherpath)
275
212
                    if mode is not None:
276
213
                        os.chmod(otherpath, mode)
290
227
        WARNING: many transports do not support this, so trying avoid using
291
228
        it if at all possible.
292
229
        """
293
 
        path = self._abspath(relpath)
 
230
        path = self.abspath(relpath)
294
231
        try:
295
 
            return [urlutils.escape(entry) for entry in os.listdir(path)]
 
232
            return [urllib.quote(entry) for entry in os.listdir(path)]
296
233
        except (IOError, OSError), e:
297
234
            self._translate_error(e, path)
298
235
 
301
238
        """
302
239
        path = relpath
303
240
        try:
304
 
            path = self._abspath(relpath)
 
241
            path = self.abspath(relpath)
305
242
            return os.stat(path)
306
243
        except (IOError, OSError),e:
307
244
            self._translate_error(e, path)
313
250
        from bzrlib.lock import ReadLock
314
251
        path = relpath
315
252
        try:
316
 
            path = self._abspath(relpath)
 
253
            path = self.abspath(relpath)
317
254
            return ReadLock(path)
318
255
        except (IOError, OSError), e:
319
256
            self._translate_error(e, path)
325
262
        :return: A lock object, which should be passed to Transport.unlock()
326
263
        """
327
264
        from bzrlib.lock import WriteLock
328
 
        return WriteLock(self._abspath(relpath))
 
265
        return WriteLock(self.abspath(relpath))
329
266
 
330
267
    def rmdir(self, relpath):
331
268
        """See Transport.rmdir."""
332
269
        path = relpath
333
270
        try:
334
 
            path = self._abspath(relpath)
 
271
            path = self.abspath(relpath)
335
272
            os.rmdir(path)
336
273
        except (IOError, OSError),e:
337
274
            self._translate_error(e, path)
344
281
            return True
345
282
 
346
283
 
 
284
class ScratchTransport(LocalTransport):
 
285
    """A transport that works in a temporary dir and cleans up after itself.
 
286
    
 
287
    The dir only exists for the lifetime of the Python object.
 
288
    Obviously you should not put anything precious in it.
 
289
    """
 
290
 
 
291
    def __init__(self, base=None):
 
292
        if base is None:
 
293
            base = tempfile.mkdtemp()
 
294
        super(ScratchTransport, self).__init__(base)
 
295
 
 
296
    def __del__(self):
 
297
        rmtree(self.base, ignore_errors=True)
 
298
        mutter("%r destroyed" % self)
 
299
 
 
300
 
347
301
class LocalRelpathServer(Server):
348
302
    """A pretend server for local transports, using relpaths."""
349
303
 
365
319
 
366
320
    def get_url(self):
367
321
        """See Transport.Server.get_url."""
368
 
        return urlutils.local_path_to_url('')
 
322
        # FIXME: \ to / on windows
 
323
        return "file://%s" % os.path.abspath("")
369
324
 
370
325
 
371
326
def get_test_permutations():