~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

Fix BzrDir.create_workingtree for NULL_REVISION

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
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."""
 
19
This is a fairly thin wrapper on regular file IO.
 
20
"""
20
21
 
21
22
import os
22
23
import shutil
23
 
from stat import ST_MODE, S_ISDIR, ST_SIZE
 
24
import sys
 
25
from stat import ST_MODE, S_ISDIR, ST_SIZE, S_IMODE
24
26
import tempfile
25
 
import urllib
26
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
27
35
from bzrlib.trace import mutter
28
 
from bzrlib.transport import Transport
29
 
from bzrlib.osutils import abspath, realpath, normpath, pathjoin, rename
 
36
from bzrlib.transport import Transport, Server
 
37
 
 
38
 
 
39
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY
30
40
 
31
41
 
32
42
class LocalTransport(Transport):
34
44
 
35
45
    def __init__(self, base):
36
46
        """Set the base path where files will be stored."""
37
 
        if base.startswith('file://'):
38
 
            base = base[7:]
39
 
        # realpath is incompatible with symlinks. When we traverse
40
 
        # up we might be able to normpath stuff. RBC 20051003
41
 
        super(LocalTransport, self).__init__(normpath(abspath(base)))
 
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)
 
56
        if base[-1] != '/':
 
57
            base = base + '/'
 
58
        super(LocalTransport, self).__init__(base)
 
59
        self._local_base = urlutils.local_path_from_url(base)
42
60
 
43
61
    def should_cache(self):
44
62
        return False
53
71
        else:
54
72
            return LocalTransport(self.abspath(offset))
55
73
 
 
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
 
56
85
    def abspath(self, relpath):
57
 
        """Return the full url to the given relative URL.
58
 
        This can be supplied with a string or a list
59
 
        """
 
86
        """Return the full url to the given relative URL."""
 
87
        # TODO: url escape the result. RBC 20060523.
60
88
        assert isinstance(relpath, basestring), (type(relpath), relpath)
61
 
        return pathjoin(self.base, urllib.unquote(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)
62
108
 
63
109
    def relpath(self, abspath):
64
110
        """Return the local path portion from a given absolute path.
65
111
        """
66
 
        from bzrlib.osutils import relpath
67
112
        if abspath is None:
68
113
            abspath = u'.'
69
 
        return relpath(self.base, abspath)
 
114
 
 
115
        return urlutils.file_relpath(
 
116
            urlutils.strip_trailing_slash(self.base), 
 
117
            urlutils.strip_trailing_slash(abspath))
70
118
 
71
119
    def has(self, relpath):
72
 
        return os.access(self.abspath(relpath), os.F_OK)
 
120
        return os.access(self._abspath(relpath), os.F_OK)
73
121
 
74
122
    def get(self, relpath):
75
123
        """Get the file at the given relative path.
77
125
        :param relpath: The relative path to the file
78
126
        """
79
127
        try:
80
 
            path = self.abspath(relpath)
 
128
            path = self._abspath(relpath)
81
129
            return open(path, 'rb')
82
130
        except (IOError, OSError),e:
83
131
            self._translate_error(e, path)
92
140
 
93
141
        path = relpath
94
142
        try:
95
 
            path = self.abspath(relpath)
 
143
            path = self._abspath(relpath)
 
144
            check_legal_path(path)
96
145
            fp = AtomicFile(path, 'wb', new_mode=mode)
97
146
        except (IOError, OSError),e:
98
147
            self._translate_error(e, path)
106
155
        """Iter the relative paths of files in the transports sub-tree."""
107
156
        queue = list(self.list_dir(u'.'))
108
157
        while queue:
109
 
            relpath = urllib.quote(queue.pop(0))
 
158
            relpath = queue.pop(0)
110
159
            st = self.stat(relpath)
111
160
            if S_ISDIR(st[ST_MODE]):
112
161
                for i, basename in enumerate(self.list_dir(relpath)):
118
167
        """Create a directory at the given path."""
119
168
        path = relpath
120
169
        try:
121
 
            path = self.abspath(relpath)
122
 
            os.mkdir(path)
 
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)
123
177
            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
124
180
                os.chmod(path, mode)
125
181
        except (IOError, OSError),e:
126
182
            self._translate_error(e, path)
127
183
 
128
 
    def append(self, relpath, f):
129
 
        """Append the text in the file-like object into the final
130
 
        location.
131
 
        """
132
 
        fp = open(self.abspath(relpath), 'ab')
133
 
        self._pump(f, fp)
 
184
    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
 
192
        try:
 
193
            fd = os.open(abspath, _append_flags, local_mode)
 
194
        except (IOError, OSError),e:
 
195
            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)
 
206
        return result
 
207
 
 
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)
134
216
 
135
217
    def copy(self, rel_from, rel_to):
136
218
        """Copy the item at rel_from to the location at rel_to"""
137
 
        import shutil
138
 
        path_from = self.abspath(rel_from)
139
 
        path_to = self.abspath(rel_to)
 
219
        path_from = self._abspath(rel_from)
 
220
        path_to = self._abspath(rel_to)
140
221
        try:
141
222
            shutil.copy(path_from, path_to)
142
223
        except (IOError, OSError),e:
143
224
            # TODO: What about path_to?
144
225
            self._translate_error(e, path_from)
145
226
 
 
227
    def rename(self, rel_from, rel_to):
 
228
        path_from = self._abspath(rel_from)
 
229
        try:
 
230
            # *don't* call bzrlib.osutils.rename, because we want to 
 
231
            # detect errors on rename
 
232
            os.rename(path_from, self._abspath(rel_to))
 
233
        except (IOError, OSError),e:
 
234
            # TODO: What about path_to?
 
235
            self._translate_error(e, path_from)
 
236
 
146
237
    def move(self, rel_from, rel_to):
147
238
        """Move the item at rel_from to the location at rel_to"""
148
 
        path_from = self.abspath(rel_from)
149
 
        path_to = self.abspath(rel_to)
 
239
        path_from = self._abspath(rel_from)
 
240
        path_to = self._abspath(rel_to)
150
241
 
151
242
        try:
 
243
            # this version will delete the destination if necessary
152
244
            rename(path_from, path_to)
153
245
        except (IOError, OSError),e:
154
246
            # TODO: What about path_to?
158
250
        """Delete the item at relpath"""
159
251
        path = relpath
160
252
        try:
161
 
            path = self.abspath(relpath)
 
253
            path = self._abspath(relpath)
162
254
            os.remove(path)
163
255
        except (IOError, OSError),e:
164
 
            # TODO: What about path_to?
165
256
            self._translate_error(e, path)
166
257
 
167
258
    def copy_to(self, relpaths, other, mode=None, pb=None):
173
264
            # Both from & to are on the local filesystem
174
265
            # Unfortunately, I can't think of anything faster than just
175
266
            # copying them across, one by one :(
176
 
            import shutil
177
 
 
178
267
            total = self._get_total(relpaths)
179
268
            count = 0
180
269
            for path in relpaths:
181
270
                self._update_pb(pb, 'copy-to', count, total)
182
271
                try:
183
 
                    mypath = self.abspath(path)
184
 
                    otherpath = other.abspath(path)
 
272
                    mypath = self._abspath(path)
 
273
                    otherpath = other._abspath(path)
185
274
                    shutil.copy(mypath, otherpath)
186
275
                    if mode is not None:
187
276
                        os.chmod(otherpath, mode)
201
290
        WARNING: many transports do not support this, so trying avoid using
202
291
        it if at all possible.
203
292
        """
204
 
        path = relpath
 
293
        path = self._abspath(relpath)
205
294
        try:
206
 
            path = self.abspath(relpath)
207
 
            return os.listdir(path)
208
 
        except (IOError, OSError),e:
 
295
            entries = os.listdir(path)
 
296
        except (IOError, OSError), e:
209
297
            self._translate_error(e, path)
 
298
        return [urlutils.escape(entry) for entry in entries]
210
299
 
211
300
    def stat(self, relpath):
212
301
        """Return the stat information for a file.
213
302
        """
214
303
        path = relpath
215
304
        try:
216
 
            path = self.abspath(relpath)
 
305
            path = self._abspath(relpath)
217
306
            return os.stat(path)
218
307
        except (IOError, OSError),e:
219
308
            self._translate_error(e, path)
223
312
        :return: A lock object, which should be passed to Transport.unlock()
224
313
        """
225
314
        from bzrlib.lock import ReadLock
226
 
        return ReadLock(self.abspath(relpath))
 
315
        path = relpath
 
316
        try:
 
317
            path = self._abspath(relpath)
 
318
            return ReadLock(path)
 
319
        except (IOError, OSError), e:
 
320
            self._translate_error(e, path)
227
321
 
228
322
    def lock_write(self, relpath):
229
323
        """Lock the given file for exclusive (write) access.
232
326
        :return: A lock object, which should be passed to Transport.unlock()
233
327
        """
234
328
        from bzrlib.lock import WriteLock
235
 
        return WriteLock(self.abspath(relpath))
236
 
 
237
 
 
238
 
class ScratchTransport(LocalTransport):
239
 
    """A transport that works in a temporary dir and cleans up after itself.
240
 
    
241
 
    The dir only exists for the lifetime of the Python object.
242
 
    Obviously you should not put anything precious in it.
243
 
    """
244
 
 
245
 
    def __init__(self, base=None):
246
 
        if base is None:
247
 
            base = tempfile.mkdtemp()
248
 
        super(ScratchTransport, self).__init__(base)
249
 
 
250
 
    def __del__(self):
251
 
        shutil.rmtree(self.base, ignore_errors=True)
252
 
        mutter("%r destroyed" % self)
 
329
        return WriteLock(self._abspath(relpath))
 
330
 
 
331
    def rmdir(self, relpath):
 
332
        """See Transport.rmdir."""
 
333
        path = relpath
 
334
        try:
 
335
            path = self._abspath(relpath)
 
336
            os.rmdir(path)
 
337
        except (IOError, OSError),e:
 
338
            self._translate_error(e, path)
 
339
 
 
340
    def _can_roundtrip_unix_modebits(self):
 
341
        if sys.platform == 'win32':
 
342
            # anyone else?
 
343
            return False
 
344
        else:
 
345
            return True
 
346
 
 
347
 
 
348
class LocalRelpathServer(Server):
 
349
    """A pretend server for local transports, using relpaths."""
 
350
 
 
351
    def get_url(self):
 
352
        """See Transport.Server.get_url."""
 
353
        return "."
 
354
 
 
355
 
 
356
class LocalAbspathServer(Server):
 
357
    """A pretend server for local transports, using absolute paths."""
 
358
 
 
359
    def get_url(self):
 
360
        """See Transport.Server.get_url."""
 
361
        return os.path.abspath("")
 
362
 
 
363
 
 
364
class LocalURLServer(Server):
 
365
    """A pretend server for local transports, using file:// urls."""
 
366
 
 
367
    def get_url(self):
 
368
        """See Transport.Server.get_url."""
 
369
        return urlutils.local_path_to_url('')
 
370
 
 
371
 
 
372
def get_test_permutations():
 
373
    """Return the permutations to be used in testing."""
 
374
    return [(LocalTransport, LocalRelpathServer),
 
375
            (LocalTransport, LocalAbspathServer),
 
376
            (LocalTransport, LocalURLServer),
 
377
            ]