~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Alexander Belchenko
  • Date: 2006-07-30 06:52:39 UTC
  • mto: (1711.2.111 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1906.
  • Revision ID: bialix@ukr.net-20060730065239-03b4ac02d9f56202
branding: change Bazaar-NG to Bazaar

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
"""\
3
 
An implementation of the Transport object for local
4
 
filesystem access.
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
 
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
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.
5
20
"""
6
21
 
7
 
from bzrlib.transport import Transport, register_transport, \
8
 
    TransportError, NoSuchFile, FileExists
9
 
import os, errno
10
 
 
11
 
class LocalTransportError(TransportError):
12
 
    pass
 
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
 
13
35
 
14
36
class LocalTransport(Transport):
15
37
    """This is the transport agent for local filesystem access."""
16
38
 
17
39
    def __init__(self, base):
18
40
        """Set the base path where files will be stored."""
19
 
        if base.startswith('file://'):
20
 
            base = base[7:]
21
 
        super(LocalTransport, self).__init__(os.path.realpath(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)
 
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)
22
55
 
23
56
    def should_cache(self):
24
57
        return False
33
66
        else:
34
67
            return LocalTransport(self.abspath(offset))
35
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
 
36
80
    def abspath(self, relpath):
37
 
        """Return the full url to the given relative path.
38
 
        This can be supplied with a string or a list
 
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.
39
99
        """
40
 
        if isinstance(relpath, basestring):
41
 
            relpath = [relpath]
42
 
        return os.path.join(self.base, *relpath)
 
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)
43
103
 
44
104
    def relpath(self, abspath):
45
105
        """Return the local path portion from a given absolute path.
46
106
        """
47
 
        from branch import _relpath
48
 
        return _relpath(self.base, abspath)
 
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))
49
113
 
50
114
    def has(self, relpath):
51
 
        return os.access(self.abspath(relpath), os.F_OK)
 
115
        return os.access(self._abspath(relpath), os.F_OK)
52
116
 
53
117
    def get(self, relpath):
54
118
        """Get the file at the given relative path.
56
120
        :param relpath: The relative path to the file
57
121
        """
58
122
        try:
59
 
            path = self.abspath(relpath)
 
123
            path = self._abspath(relpath)
60
124
            return open(path, 'rb')
61
 
        except IOError,e:
62
 
            if e.errno == errno.ENOENT:
63
 
                raise NoSuchFile('File %r does not exist' % path, orig_error=e)
64
 
            raise LocalTransportError(orig_error=e)
 
125
        except (IOError, OSError),e:
 
126
            self._translate_error(e, path)
65
127
 
66
 
    def put(self, relpath, f):
 
128
    def put(self, relpath, f, mode=None):
67
129
        """Copy the file-like or string object into the location.
68
130
 
69
131
        :param relpath: Location to put the contents, relative to base.
71
133
        """
72
134
        from bzrlib.atomicfile import AtomicFile
73
135
 
 
136
        path = relpath
74
137
        try:
75
 
            path = self.abspath(relpath)
76
 
            fp = AtomicFile(path, 'wb')
77
 
        except IOError, e:
78
 
            if e.errno == errno.ENOENT:
79
 
                raise NoSuchFile('File %r does not exist' % path, orig_error=e)
80
 
            raise LocalTransportError(orig_error=e)
 
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)
81
143
        try:
82
144
            self._pump(f, fp)
83
145
            fp.commit()
84
146
        finally:
85
147
            fp.close()
86
148
 
87
 
    def mkdir(self, relpath):
 
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):
88
162
        """Create a directory at the given path."""
 
163
        path = relpath
89
164
        try:
90
 
            os.mkdir(self.abspath(relpath))
91
 
        except OSError,e:
92
 
            if e.errno == errno.EEXIST:
93
 
                raise FileExists(orig_error=e)
94
 
            elif e.errno == errno.ENOENT:
95
 
                raise NoSuchFile(orig_error=e)
96
 
            raise LocalTransportError(orig_error=e)
 
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)
97
171
 
98
 
    def append(self, relpath, f):
99
 
        """Append the text in the file-like object into the final
100
 
        location.
101
 
        """
102
 
        fp = open(self.abspath(relpath), 'ab')
103
 
        self._pump(f, fp)
 
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
104
192
 
105
193
    def copy(self, rel_from, rel_to):
106
194
        """Copy the item at rel_from to the location at rel_to"""
107
 
        import shutil
108
 
        path_from = self.abspath(rel_from)
109
 
        path_to = self.abspath(rel_to)
 
195
        path_from = self._abspath(rel_from)
 
196
        path_to = self._abspath(rel_to)
110
197
        try:
111
198
            shutil.copy(path_from, path_to)
112
 
        except OSError,e:
113
 
            raise LocalTransportError(orig_error=e)
 
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)
114
212
 
115
213
    def move(self, rel_from, rel_to):
116
214
        """Move the item at rel_from to the location at rel_to"""
117
 
        path_from = self.abspath(rel_from)
118
 
        path_to = self.abspath(rel_to)
 
215
        path_from = self._abspath(rel_from)
 
216
        path_to = self._abspath(rel_to)
119
217
 
120
218
        try:
121
 
            os.rename(path_from, path_to)
122
 
        except OSError,e:
123
 
            raise LocalTransportError(orig_error=e)
 
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)
124
224
 
125
225
    def delete(self, relpath):
126
226
        """Delete the item at relpath"""
 
227
        path = relpath
127
228
        try:
128
 
            os.remove(self.abspath(relpath))
129
 
        except OSError,e:
130
 
            raise LocalTransportError(orig_error=e)
 
229
            path = self._abspath(relpath)
 
230
            os.remove(path)
 
231
        except (IOError, OSError),e:
 
232
            self._translate_error(e, path)
131
233
 
132
 
    def copy_to(self, relpaths, other, pb=None):
 
234
    def copy_to(self, relpaths, other, mode=None, pb=None):
133
235
        """Copy a set of entries from self into another Transport.
134
236
 
135
237
        :param relpaths: A list/generator of entries to be copied.
138
240
            # Both from & to are on the local filesystem
139
241
            # Unfortunately, I can't think of anything faster than just
140
242
            # copying them across, one by one :(
141
 
            import shutil
142
 
 
143
243
            total = self._get_total(relpaths)
144
244
            count = 0
145
245
            for path in relpaths:
146
246
                self._update_pb(pb, 'copy-to', count, total)
147
 
                shutil.copy(self.abspath(path), other.abspath(path))
 
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)
148
255
                count += 1
149
256
            return count
150
257
        else:
151
 
            return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
152
 
 
153
 
 
154
 
    def async_get(self, relpath):
155
 
        """Make a request for an file at the given location, but
156
 
        don't worry about actually getting it yet.
157
 
 
158
 
        :rtype: AsyncFile
159
 
        """
160
 
        raise NotImplementedError
 
258
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
 
259
 
 
260
    def listable(self):
 
261
        """See Transport.listable."""
 
262
        return True
161
263
 
162
264
    def list_dir(self, relpath):
163
265
        """Return a list of all files at the given location.
164
266
        WARNING: many transports do not support this, so trying avoid using
165
267
        it if at all possible.
166
268
        """
 
269
        path = self._abspath(relpath)
167
270
        try:
168
 
            return os.listdir(self.abspath(relpath))
169
 
        except OSError,e:
170
 
            raise LocalTransportError(orig_error=e)
 
271
            return [urlutils.escape(entry) for entry in os.listdir(path)]
 
272
        except (IOError, OSError), e:
 
273
            self._translate_error(e, path)
171
274
 
172
275
    def stat(self, relpath):
173
276
        """Return the stat information for a file.
174
277
        """
 
278
        path = relpath
175
279
        try:
176
 
            return os.stat(self.abspath(relpath))
177
 
        except OSError,e:
178
 
            raise LocalTransportError(orig_error=e)
 
280
            path = self._abspath(relpath)
 
281
            return os.stat(path)
 
282
        except (IOError, OSError),e:
 
283
            self._translate_error(e, path)
179
284
 
180
285
    def lock_read(self, relpath):
181
286
        """Lock the given file for shared (read) access.
182
287
        :return: A lock object, which should be passed to Transport.unlock()
183
288
        """
184
289
        from bzrlib.lock import ReadLock
185
 
        return ReadLock(self.abspath(relpath))
 
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)
186
296
 
187
297
    def lock_write(self, relpath):
188
298
        """Lock the given file for exclusive (write) access.
191
301
        :return: A lock object, which should be passed to Transport.unlock()
192
302
        """
193
303
        from bzrlib.lock import WriteLock
194
 
        return WriteLock(self.abspath(relpath))
195
 
 
196
 
# If nothing else matches, try the LocalTransport
197
 
register_transport(None, LocalTransport)
198
 
register_transport('file://', LocalTransport)
 
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
            ]