~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Jamie Wilkinson
  • Date: 2006-07-18 23:59:52 UTC
  • mfrom: (1868 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1874.
  • Revision ID: jaq@spacepants.org-20060718235952-1e362401a7858958
merge from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Transport for the local filesystem.
18
18
 
19
 
This is a fairly thin wrapper on regular file IO."""
 
19
This is a fairly thin wrapper on regular file IO.
 
20
"""
20
21
 
21
22
import os
22
23
import shutil
23
24
import sys
24
25
from stat import ST_MODE, S_ISDIR, ST_SIZE
25
26
import tempfile
26
 
import urllib
27
27
 
 
28
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename, 
 
29
                            check_legal_path, rmtree)
 
30
from bzrlib.symbol_versioning import warn
28
31
from bzrlib.trace import mutter
29
32
from bzrlib.transport import Transport, Server
30
 
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename, 
31
 
                            check_legal_path, rmtree)
 
33
import bzrlib.urlutils as urlutils
32
34
 
33
35
 
34
36
class LocalTransport(Transport):
36
38
 
37
39
    def __init__(self, base):
38
40
        """Set the base path where files will be stored."""
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))
 
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)
44
50
        if base[-1] != '/':
45
51
            base = base + '/'
46
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)
47
55
 
48
56
    def should_cache(self):
49
57
        return False
58
66
        else:
59
67
            return LocalTransport(self.abspath(offset))
60
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
 
61
80
    def abspath(self, relpath):
62
81
        """Return the full url to the given relative URL."""
 
82
        # TODO: url escape the result. RBC 20060523.
63
83
        assert isinstance(relpath, basestring), (type(relpath), relpath)
64
 
        result = normpath(pathjoin(self.base, urllib.unquote(relpath)))
65
 
        #if result[-1] != '/':
66
 
        #    result += '/'
67
 
        return result
 
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.
 
99
        """
 
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)
68
103
 
69
104
    def relpath(self, abspath):
70
105
        """Return the local path portion from a given absolute path.
71
106
        """
72
 
        from bzrlib.osutils import relpath, strip_trailing_slash
73
107
        if abspath is None:
74
108
            abspath = u'.'
75
109
 
76
 
        return relpath(strip_trailing_slash(self.base), 
77
 
                       strip_trailing_slash(abspath))
 
110
        return urlutils.file_relpath(
 
111
            urlutils.strip_trailing_slash(self.base), 
 
112
            urlutils.strip_trailing_slash(abspath))
78
113
 
79
114
    def has(self, relpath):
80
 
        return os.access(self.abspath(relpath), os.F_OK)
 
115
        return os.access(self._abspath(relpath), os.F_OK)
81
116
 
82
117
    def get(self, relpath):
83
118
        """Get the file at the given relative path.
85
120
        :param relpath: The relative path to the file
86
121
        """
87
122
        try:
88
 
            path = self.abspath(relpath)
 
123
            path = self._abspath(relpath)
89
124
            return open(path, 'rb')
90
125
        except (IOError, OSError),e:
91
126
            self._translate_error(e, path)
100
135
 
101
136
        path = relpath
102
137
        try:
103
 
            path = self.abspath(relpath)
 
138
            path = self._abspath(relpath)
104
139
            check_legal_path(path)
105
140
            fp = AtomicFile(path, 'wb', new_mode=mode)
106
141
        except (IOError, OSError),e:
127
162
        """Create a directory at the given path."""
128
163
        path = relpath
129
164
        try:
130
 
            path = self.abspath(relpath)
 
165
            path = self._abspath(relpath)
131
166
            os.mkdir(path)
132
167
            if mode is not None:
133
168
                os.chmod(path, mode)
135
170
            self._translate_error(e, path)
136
171
 
137
172
    def append(self, relpath, f, mode=None):
138
 
        """Append the text in the file-like object into the final
139
 
        location.
140
 
        """
 
173
        """Append the text in the file-like object into the final location."""
 
174
        abspath = self._abspath(relpath)
 
175
        fp = None
141
176
        try:
142
 
            fp = open(self.abspath(relpath), 'ab')
143
 
            if mode is not None:
144
 
                os.chmod(self.abspath(relpath), mode)
145
 
        except (IOError, OSError),e:
146
 
            self._translate_error(e, relpath)
147
 
        # win32 workaround (tell on an unwritten file returns 0)
148
 
        fp.seek(0, 2)
149
 
        result = fp.tell()
150
 
        self._pump(f, fp)
 
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()
151
191
        return result
152
192
 
153
193
    def copy(self, rel_from, rel_to):
154
194
        """Copy the item at rel_from to the location at rel_to"""
155
 
        path_from = self.abspath(rel_from)
156
 
        path_to = self.abspath(rel_to)
 
195
        path_from = self._abspath(rel_from)
 
196
        path_to = self._abspath(rel_to)
157
197
        try:
158
198
            shutil.copy(path_from, path_to)
159
199
        except (IOError, OSError),e:
161
201
            self._translate_error(e, path_from)
162
202
 
163
203
    def rename(self, rel_from, rel_to):
164
 
        path_from = self.abspath(rel_from)
 
204
        path_from = self._abspath(rel_from)
165
205
        try:
166
206
            # *don't* call bzrlib.osutils.rename, because we want to 
167
207
            # detect errors on rename
168
 
            os.rename(path_from, self.abspath(rel_to))
 
208
            os.rename(path_from, self._abspath(rel_to))
169
209
        except (IOError, OSError),e:
170
210
            # TODO: What about path_to?
171
211
            self._translate_error(e, path_from)
172
212
 
173
213
    def move(self, rel_from, rel_to):
174
214
        """Move the item at rel_from to the location at rel_to"""
175
 
        path_from = self.abspath(rel_from)
176
 
        path_to = self.abspath(rel_to)
 
215
        path_from = self._abspath(rel_from)
 
216
        path_to = self._abspath(rel_to)
177
217
 
178
218
        try:
179
219
            # this version will delete the destination if necessary
186
226
        """Delete the item at relpath"""
187
227
        path = relpath
188
228
        try:
189
 
            path = self.abspath(relpath)
 
229
            path = self._abspath(relpath)
190
230
            os.remove(path)
191
231
        except (IOError, OSError),e:
192
 
            # TODO: What about path_to?
193
232
            self._translate_error(e, path)
194
233
 
195
234
    def copy_to(self, relpaths, other, mode=None, pb=None):
206
245
            for path in relpaths:
207
246
                self._update_pb(pb, 'copy-to', count, total)
208
247
                try:
209
 
                    mypath = self.abspath(path)
210
 
                    otherpath = other.abspath(path)
 
248
                    mypath = self._abspath(path)
 
249
                    otherpath = other._abspath(path)
211
250
                    shutil.copy(mypath, otherpath)
212
251
                    if mode is not None:
213
252
                        os.chmod(otherpath, mode)
227
266
        WARNING: many transports do not support this, so trying avoid using
228
267
        it if at all possible.
229
268
        """
230
 
        path = self.abspath(relpath)
 
269
        path = self._abspath(relpath)
231
270
        try:
232
 
            return [urllib.quote(entry) for entry in os.listdir(path)]
 
271
            return [urlutils.escape(entry) for entry in os.listdir(path)]
233
272
        except (IOError, OSError), e:
234
273
            self._translate_error(e, path)
235
274
 
238
277
        """
239
278
        path = relpath
240
279
        try:
241
 
            path = self.abspath(relpath)
 
280
            path = self._abspath(relpath)
242
281
            return os.stat(path)
243
282
        except (IOError, OSError),e:
244
283
            self._translate_error(e, path)
250
289
        from bzrlib.lock import ReadLock
251
290
        path = relpath
252
291
        try:
253
 
            path = self.abspath(relpath)
 
292
            path = self._abspath(relpath)
254
293
            return ReadLock(path)
255
294
        except (IOError, OSError), e:
256
295
            self._translate_error(e, path)
262
301
        :return: A lock object, which should be passed to Transport.unlock()
263
302
        """
264
303
        from bzrlib.lock import WriteLock
265
 
        return WriteLock(self.abspath(relpath))
 
304
        return WriteLock(self._abspath(relpath))
266
305
 
267
306
    def rmdir(self, relpath):
268
307
        """See Transport.rmdir."""
269
308
        path = relpath
270
309
        try:
271
 
            path = self.abspath(relpath)
 
310
            path = self._abspath(relpath)
272
311
            os.rmdir(path)
273
312
        except (IOError, OSError),e:
274
313
            self._translate_error(e, path)
281
320
            return True
282
321
 
283
322
 
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
 
 
301
323
class LocalRelpathServer(Server):
302
324
    """A pretend server for local transports, using relpaths."""
303
325
 
319
341
 
320
342
    def get_url(self):
321
343
        """See Transport.Server.get_url."""
322
 
        # FIXME: \ to / on windows
323
 
        return "file://%s" % os.path.abspath("")
 
344
        return urlutils.local_path_to_url('')
324
345
 
325
346
 
326
347
def get_test_permutations():