~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

Test case for discrepancy between fileid_involved and compare_tree [recommit]

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005 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
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
 
import sys
25
23
from stat import ST_MODE, S_ISDIR, ST_SIZE
26
24
import tempfile
 
25
import urllib
27
26
 
28
 
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename, 
29
 
                            check_legal_path, rmtree)
30
 
from bzrlib.symbol_versioning import warn
31
27
from bzrlib.trace import mutter
32
28
from bzrlib.transport import Transport, Server
33
 
import bzrlib.urlutils as urlutils
 
29
from bzrlib.osutils import abspath, realpath, normpath, pathjoin, rename
34
30
 
35
31
 
36
32
class LocalTransport(Transport):
38
34
 
39
35
    def __init__(self, base):
40
36
        """Set the base path where files will be stored."""
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)
 
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
        base = normpath(abspath(base))
50
42
        if base[-1] != '/':
51
43
            base = base + '/'
52
44
        super(LocalTransport, self).__init__(base)
53
 
        self._local_base = urlutils.local_path_from_url(base)
54
45
 
55
46
    def should_cache(self):
56
47
        return False
65
56
        else:
66
57
            return LocalTransport(self.abspath(offset))
67
58
 
68
 
    def _abspath(self, relative_reference):
69
 
        """Return a path for use in os calls.
70
 
 
71
 
        Several assumptions are made:
72
 
         - relative_reference does not contain '..'
73
 
         - relative_reference is url escaped.
74
 
        """
75
 
        if relative_reference in ('.', ''):
76
 
            return self._local_base
77
 
        return self._local_base + urlutils.unescape(relative_reference)
78
 
 
79
59
    def abspath(self, relpath):
80
 
        """Return the full url to the given relative URL."""
81
 
        # TODO: url escape the result. RBC 20060523.
 
60
        """Return the full url to the given relative URL.
 
61
        This can be supplied with a string or a list
 
62
        """
82
63
        assert isinstance(relpath, basestring), (type(relpath), relpath)
83
 
        # jam 20060426 Using normpath on the real path, because that ensures
84
 
        #       proper handling of stuff like
85
 
        path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
86
 
        return urlutils.local_path_to_url(path)
87
 
 
88
 
    def local_abspath(self, relpath):
89
 
        """Transform the given relative path URL into the actual path on disk
90
 
 
91
 
        This function only exists for the LocalTransport, since it is
92
 
        the only one that has direct local access.
93
 
        This is mostly for stuff like WorkingTree which needs to know
94
 
        the local working directory.
95
 
        
96
 
        This function is quite expensive: it calls realpath which resolves
97
 
        symlinks.
98
 
        """
99
 
        absurl = self.abspath(relpath)
100
 
        # mutter(u'relpath %s => base: %s, absurl %s', relpath, self.base, absurl)
101
 
        return urlutils.local_path_from_url(absurl)
 
64
        return pathjoin(self.base, urllib.unquote(relpath))
102
65
 
103
66
    def relpath(self, abspath):
104
67
        """Return the local path portion from a given absolute path.
105
68
        """
 
69
        from bzrlib.osutils import relpath
106
70
        if abspath is None:
107
71
            abspath = u'.'
108
 
 
109
 
        return urlutils.file_relpath(
110
 
            urlutils.strip_trailing_slash(self.base), 
111
 
            urlutils.strip_trailing_slash(abspath))
 
72
        if abspath.endswith('/'):
 
73
            abspath = abspath[:-1]
 
74
        return relpath(self.base[:-1], abspath)
112
75
 
113
76
    def has(self, relpath):
114
 
        return os.access(self._abspath(relpath), os.F_OK)
 
77
        return os.access(self.abspath(relpath), os.F_OK)
115
78
 
116
79
    def get(self, relpath):
117
80
        """Get the file at the given relative path.
119
82
        :param relpath: The relative path to the file
120
83
        """
121
84
        try:
122
 
            path = self._abspath(relpath)
 
85
            path = self.abspath(relpath)
123
86
            return open(path, 'rb')
124
87
        except (IOError, OSError),e:
125
88
            self._translate_error(e, path)
134
97
 
135
98
        path = relpath
136
99
        try:
137
 
            path = self._abspath(relpath)
138
 
            check_legal_path(path)
 
100
            path = self.abspath(relpath)
139
101
            fp = AtomicFile(path, 'wb', new_mode=mode)
140
102
        except (IOError, OSError),e:
141
103
            self._translate_error(e, path)
149
111
        """Iter the relative paths of files in the transports sub-tree."""
150
112
        queue = list(self.list_dir(u'.'))
151
113
        while queue:
152
 
            relpath = queue.pop(0)
 
114
            relpath = urllib.quote(queue.pop(0))
153
115
            st = self.stat(relpath)
154
116
            if S_ISDIR(st[ST_MODE]):
155
117
                for i, basename in enumerate(self.list_dir(relpath)):
161
123
        """Create a directory at the given path."""
162
124
        path = relpath
163
125
        try:
164
 
            path = self._abspath(relpath)
 
126
            path = self.abspath(relpath)
165
127
            os.mkdir(path)
166
128
            if mode is not None:
167
129
                os.chmod(path, mode)
168
130
        except (IOError, OSError),e:
169
131
            self._translate_error(e, path)
170
132
 
171
 
    def append(self, relpath, f, mode=None):
172
 
        """Append the text in the file-like object into the final location."""
173
 
        abspath = self._abspath(relpath)
 
133
    def append(self, relpath, f):
 
134
        """Append the text in the file-like object into the final
 
135
        location.
 
136
        """
174
137
        try:
175
 
            fp = open(abspath, 'ab')
176
 
            # FIXME should we really be chmodding every time ? RBC 20060523
177
 
            if mode is not None:
178
 
                os.chmod(abspath, mode)
 
138
            fp = open(self.abspath(relpath), 'ab')
179
139
        except (IOError, OSError),e:
180
140
            self._translate_error(e, relpath)
181
 
        # win32 workaround (tell on an unwritten file returns 0)
182
 
        fp.seek(0, 2)
183
 
        result = fp.tell()
184
141
        self._pump(f, fp)
185
 
        return result
186
142
 
187
143
    def copy(self, rel_from, rel_to):
188
144
        """Copy the item at rel_from to the location at rel_to"""
189
 
        path_from = self._abspath(rel_from)
190
 
        path_to = self._abspath(rel_to)
 
145
        import shutil
 
146
        path_from = self.abspath(rel_from)
 
147
        path_to = self.abspath(rel_to)
191
148
        try:
192
149
            shutil.copy(path_from, path_to)
193
150
        except (IOError, OSError),e:
194
151
            # TODO: What about path_to?
195
152
            self._translate_error(e, path_from)
196
153
 
197
 
    def rename(self, rel_from, rel_to):
198
 
        path_from = self._abspath(rel_from)
199
 
        try:
200
 
            # *don't* call bzrlib.osutils.rename, because we want to 
201
 
            # detect errors on rename
202
 
            os.rename(path_from, self._abspath(rel_to))
203
 
        except (IOError, OSError),e:
204
 
            # TODO: What about path_to?
205
 
            self._translate_error(e, path_from)
206
 
 
207
154
    def move(self, rel_from, rel_to):
208
155
        """Move the item at rel_from to the location at rel_to"""
209
 
        path_from = self._abspath(rel_from)
210
 
        path_to = self._abspath(rel_to)
 
156
        path_from = self.abspath(rel_from)
 
157
        path_to = self.abspath(rel_to)
211
158
 
212
159
        try:
213
 
            # this version will delete the destination if necessary
214
160
            rename(path_from, path_to)
215
161
        except (IOError, OSError),e:
216
162
            # TODO: What about path_to?
220
166
        """Delete the item at relpath"""
221
167
        path = relpath
222
168
        try:
223
 
            path = self._abspath(relpath)
 
169
            path = self.abspath(relpath)
224
170
            os.remove(path)
225
171
        except (IOError, OSError),e:
 
172
            # TODO: What about path_to?
226
173
            self._translate_error(e, path)
227
174
 
228
175
    def copy_to(self, relpaths, other, mode=None, pb=None):
234
181
            # Both from & to are on the local filesystem
235
182
            # Unfortunately, I can't think of anything faster than just
236
183
            # copying them across, one by one :(
 
184
            import shutil
 
185
 
237
186
            total = self._get_total(relpaths)
238
187
            count = 0
239
188
            for path in relpaths:
240
189
                self._update_pb(pb, 'copy-to', count, total)
241
190
                try:
242
 
                    mypath = self._abspath(path)
243
 
                    otherpath = other._abspath(path)
 
191
                    mypath = self.abspath(path)
 
192
                    otherpath = other.abspath(path)
244
193
                    shutil.copy(mypath, otherpath)
245
194
                    if mode is not None:
246
195
                        os.chmod(otherpath, mode)
260
209
        WARNING: many transports do not support this, so trying avoid using
261
210
        it if at all possible.
262
211
        """
263
 
        path = self._abspath(relpath)
 
212
        path = relpath
264
213
        try:
265
 
            return [urlutils.escape(entry) for entry in os.listdir(path)]
266
 
        except (IOError, OSError), e:
 
214
            path = self.abspath(relpath)
 
215
            return os.listdir(path)
 
216
        except (IOError, OSError),e:
267
217
            self._translate_error(e, path)
268
218
 
269
219
    def stat(self, relpath):
271
221
        """
272
222
        path = relpath
273
223
        try:
274
 
            path = self._abspath(relpath)
 
224
            path = self.abspath(relpath)
275
225
            return os.stat(path)
276
226
        except (IOError, OSError),e:
277
227
            self._translate_error(e, path)
283
233
        from bzrlib.lock import ReadLock
284
234
        path = relpath
285
235
        try:
286
 
            path = self._abspath(relpath)
 
236
            path = self.abspath(relpath)
287
237
            return ReadLock(path)
288
238
        except (IOError, OSError), e:
289
239
            self._translate_error(e, path)
295
245
        :return: A lock object, which should be passed to Transport.unlock()
296
246
        """
297
247
        from bzrlib.lock import WriteLock
298
 
        return WriteLock(self._abspath(relpath))
 
248
        return WriteLock(self.abspath(relpath))
299
249
 
300
250
    def rmdir(self, relpath):
301
251
        """See Transport.rmdir."""
302
252
        path = relpath
303
253
        try:
304
 
            path = self._abspath(relpath)
 
254
            path = self.abspath(relpath)
305
255
            os.rmdir(path)
306
256
        except (IOError, OSError),e:
307
257
            self._translate_error(e, path)
308
258
 
309
 
    def _can_roundtrip_unix_modebits(self):
310
 
        if sys.platform == 'win32':
311
 
            # anyone else?
312
 
            return False
313
 
        else:
314
 
            return True
315
 
 
316
 
 
317
259
class ScratchTransport(LocalTransport):
318
260
    """A transport that works in a temporary dir and cleans up after itself.
319
261
    
327
269
        super(ScratchTransport, self).__init__(base)
328
270
 
329
271
    def __del__(self):
330
 
        rmtree(self.base, ignore_errors=True)
 
272
        shutil.rmtree(self.base, ignore_errors=True)
331
273
        mutter("%r destroyed" % self)
332
274
 
333
275
 
352
294
 
353
295
    def get_url(self):
354
296
        """See Transport.Server.get_url."""
355
 
        return urlutils.local_path_to_url('')
 
297
        # FIXME: \ to / on windows
 
298
        return "file://%s" % os.path.abspath("")
356
299
 
357
300
 
358
301
def get_test_permutations():