/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

[merge] robertc's integration, updated tests to check for retcode=3

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2012, 2016 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 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
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
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
 
"""
21
 
 
22
 
from __future__ import absolute_import
 
19
This is a fairly thin wrapper on regular file IO."""
23
20
 
24
21
import os
25
 
from stat import ST_MODE, S_ISDIR, S_IMODE
26
 
import sys
27
 
 
28
 
from ..lazy_import import lazy_import
29
 
lazy_import(globals(), """
30
22
import errno
31
23
import shutil
32
 
 
33
 
from breezy import (
34
 
    atomicfile,
35
 
    osutils,
36
 
    urlutils,
37
 
    )
38
 
from breezy.transport import LateReadError
39
 
""")
40
 
 
41
 
from .. import transport
42
 
 
43
 
 
44
 
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY | osutils.O_NOINHERIT
45
 
_put_non_atomic_flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | osutils.O_BINARY | osutils.O_NOINHERIT
46
 
 
47
 
 
48
 
class LocalTransport(transport.Transport):
 
24
from stat import ST_MODE, S_ISDIR, ST_SIZE
 
25
import tempfile
 
26
import urllib
 
27
 
 
28
from bzrlib.trace import mutter
 
29
from bzrlib.transport import Transport, register_transport, \
 
30
    TransportError, NoSuchFile, FileExists
 
31
from bzrlib.osutils import abspath
 
32
 
 
33
class LocalTransportError(TransportError):
 
34
    pass
 
35
 
 
36
 
 
37
class LocalTransport(Transport):
49
38
    """This is the transport agent for local filesystem access."""
50
39
 
51
40
    def __init__(self, base):
52
41
        """Set the base path where files will be stored."""
53
 
        if not base.startswith('file://'):
54
 
            raise AssertionError("not a file:// url: %r" % base)
55
 
        if base[-1] != '/':
56
 
            base = base + '/'
57
 
 
58
 
        # Special case : windows has no "root", but does have
59
 
        # multiple lettered drives inside it. #240910
60
 
        if sys.platform == 'win32' and base == 'file:///':
61
 
            base = ''
62
 
            self._local_base = ''
63
 
            super(LocalTransport, self).__init__(base)
64
 
            return
65
 
 
66
 
        super(LocalTransport, self).__init__(base)
67
 
        self._local_base = urlutils.local_path_from_url(base)
68
 
        if self._local_base[-1] != '/':
69
 
            self._local_base = self._local_base + '/'
 
42
        if base.startswith('file://'):
 
43
            base = base[7:]
 
44
        # realpath is incompatible with symlinks. When we traverse
 
45
        # up we might be able to normpath stuff. RBC 20051003
 
46
        super(LocalTransport, self).__init__(
 
47
            os.path.normpath(abspath(base)))
 
48
 
 
49
    def should_cache(self):
 
50
        return False
70
51
 
71
52
    def clone(self, offset=None):
72
53
        """Return a new LocalTransport with root at self.base + offset
73
 
        Because the local filesystem does not require a connection,
 
54
        Because the local filesystem does not require a connection, 
74
55
        we can just return a new object.
75
56
        """
76
57
        if offset is None:
77
58
            return LocalTransport(self.base)
78
59
        else:
79
 
            abspath = self.abspath(offset)
80
 
            if abspath == 'file://':
81
 
                # fix upwalk for UNC path
82
 
                # when clone from //HOST/path updir recursively
83
 
                # we should stop at least at //HOST part
84
 
                abspath = self.base
85
 
            return LocalTransport(abspath)
86
 
 
87
 
    def _abspath(self, relative_reference):
88
 
        """Return a path for use in os calls.
89
 
 
90
 
        Several assumptions are made:
91
 
         - relative_reference does not contain '..'
92
 
         - relative_reference is url escaped.
93
 
        """
94
 
        if relative_reference in ('.', ''):
95
 
            # _local_base normally has a trailing slash; strip it so that stat
96
 
            # on a transport pointing to a symlink reads the link not the
97
 
            # referent but be careful of / and c:\
98
 
            return osutils.split(self._local_base)[0]
99
 
        return self._local_base + urlutils.unescape(relative_reference)
 
60
            return LocalTransport(self.abspath(offset))
100
61
 
101
62
    def abspath(self, relpath):
102
 
        """Return the full url to the given relative URL."""
103
 
        # TODO: url escape the result. RBC 20060523.
104
 
        # jam 20060426 Using normpath on the real path, because that ensures
105
 
        #       proper handling of stuff like
106
 
        path = osutils.normpath(osutils.pathjoin(
107
 
            self._local_base, urlutils.unescape(relpath)))
108
 
        # on windows, our _local_base may or may not have a drive specified
109
 
        # (ie, it may be "/" or "c:/foo").
110
 
        # If 'relpath' is '/' we *always* get back an abspath without
111
 
        # the drive letter - but if our transport already has a drive letter,
112
 
        # we want our abspaths to have a drive letter too - so handle that
113
 
        # here.
114
 
        if (sys.platform == "win32" and self._local_base[1:2] == ":"
115
 
                and path == '/'):
116
 
            path = self._local_base[:3]
117
 
 
118
 
        return urlutils.local_path_to_url(path)
119
 
 
120
 
    def local_abspath(self, relpath):
121
 
        """Transform the given relative path URL into the actual path on disk
122
 
 
123
 
        This function only exists for the LocalTransport, since it is
124
 
        the only one that has direct local access.
125
 
        This is mostly for stuff like WorkingTree which needs to know
126
 
        the local working directory.  The returned path will always contain
127
 
        forward slashes as the path separator, regardless of the platform.
128
 
 
129
 
        This function is quite expensive: it calls realpath which resolves
130
 
        symlinks.
 
63
        """Return the full url to the given relative URL.
 
64
        This can be supplied with a string or a list
131
65
        """
132
 
        absurl = self.abspath(relpath)
133
 
        # mutter(u'relpath %s => base: %s, absurl %s', relpath, self.base, absurl)
134
 
        return urlutils.local_path_from_url(absurl)
 
66
        assert isinstance(relpath, basestring), (type(relpath), relpath)
 
67
        return os.path.join(self.base, urllib.unquote(relpath))
135
68
 
136
69
    def relpath(self, abspath):
137
70
        """Return the local path portion from a given absolute path.
138
71
        """
 
72
        from bzrlib.osutils import relpath
139
73
        if abspath is None:
140
 
            abspath = u'.'
141
 
 
142
 
        return urlutils.file_relpath(self.base, abspath)
 
74
            abspath = '.'
 
75
        return relpath(self.base, abspath)
143
76
 
144
77
    def has(self, relpath):
145
 
        return os.access(self._abspath(relpath), os.F_OK)
 
78
        return os.access(self.abspath(relpath), os.F_OK)
146
79
 
147
80
    def get(self, relpath):
148
81
        """Get the file at the given relative path.
149
82
 
150
83
        :param relpath: The relative path to the file
151
84
        """
152
 
        canonical_url = self.abspath(relpath)
153
 
        if canonical_url in transport._file_streams:
154
 
            transport._file_streams[canonical_url].flush()
155
 
        try:
156
 
            path = self._abspath(relpath)
157
 
            return osutils.open_file(path, 'rb')
158
 
        except (IOError, OSError) as e:
159
 
            if e.errno == errno.EISDIR:
160
 
                return LateReadError(relpath)
161
 
            self._translate_error(e, path)
162
 
 
163
 
    def put_file(self, relpath, f, mode=None):
164
 
        """Copy the file-like object into the location.
165
 
 
166
 
        :param relpath: Location to put the contents, relative to base.
167
 
        :param f:       File-like object.
168
 
        :param mode: The mode for the newly created file,
169
 
                     None means just use the default
170
 
        """
171
 
 
172
 
        path = relpath
173
 
        try:
174
 
            path = self._abspath(relpath)
175
 
            osutils.check_legal_path(path)
176
 
            fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
177
 
        except (IOError, OSError) as e:
178
 
            self._translate_error(e, path)
179
 
        try:
180
 
            length = self._pump(f, fp)
181
 
            fp.commit()
182
 
        finally:
183
 
            fp.close()
184
 
        return length
185
 
 
186
 
    def put_bytes(self, relpath, raw_bytes, mode=None):
187
 
        """Copy the string into the location.
188
 
 
189
 
        :param relpath: Location to put the contents, relative to base.
190
 
        :param raw_bytes:   String
191
 
        """
192
 
        if not isinstance(raw_bytes, bytes):
193
 
            raise TypeError(
194
 
                'raw_bytes must be bytes, not %s' % type(raw_bytes))
195
 
        path = relpath
196
 
        try:
197
 
            path = self._abspath(relpath)
198
 
            osutils.check_legal_path(path)
199
 
            fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
200
 
        except (IOError, OSError) as e:
201
 
            self._translate_error(e, path)
202
 
        try:
203
 
            if bytes:
204
 
                fp.write(raw_bytes)
205
 
            fp.commit()
206
 
        finally:
207
 
            fp.close()
208
 
 
209
 
    def _put_non_atomic_helper(self, relpath, writer,
210
 
                               mode=None,
211
 
                               create_parent_dir=False,
212
 
                               dir_mode=None):
213
 
        """Common functionality information for the put_*_non_atomic.
214
 
 
215
 
        This tracks all the create_parent_dir stuff.
216
 
 
217
 
        :param relpath: the path we are putting to.
218
 
        :param writer: A function that takes an os level file descriptor
219
 
            and writes whatever data it needs to write there.
220
 
        :param mode: The final file mode.
221
 
        :param create_parent_dir: Should we be creating the parent directory
222
 
            if it doesn't exist?
223
 
        """
224
 
        abspath = self._abspath(relpath)
225
 
        if mode is None:
226
 
            # os.open() will automatically use the umask
227
 
            local_mode = 0o666
228
 
        else:
229
 
            local_mode = mode
230
 
        try:
231
 
            fd = os.open(abspath, _put_non_atomic_flags, local_mode)
232
 
        except (IOError, OSError) as e:
233
 
            # We couldn't create the file, maybe we need to create
234
 
            # the parent directory, and try again
235
 
            if (not create_parent_dir
236
 
                    or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
237
 
                self._translate_error(e, relpath)
238
 
            parent_dir = os.path.dirname(abspath)
239
 
            if not parent_dir:
240
 
                self._translate_error(e, relpath)
241
 
            self._mkdir(parent_dir, mode=dir_mode)
242
 
            # We created the parent directory, lets try to open the
243
 
            # file again
244
 
            try:
245
 
                fd = os.open(abspath, _put_non_atomic_flags, local_mode)
246
 
            except (IOError, OSError) as e:
247
 
                self._translate_error(e, relpath)
248
 
        try:
249
 
            st = os.fstat(fd)
250
 
            if mode is not None and mode != S_IMODE(st.st_mode):
251
 
                # Because of umask, we may still need to chmod the file.
252
 
                # But in the general case, we won't have to
253
 
                osutils.chmod_if_possible(abspath, mode)
254
 
            writer(fd)
255
 
        finally:
256
 
            os.close(fd)
257
 
 
258
 
    def put_file_non_atomic(self, relpath, f, mode=None,
259
 
                            create_parent_dir=False,
260
 
                            dir_mode=None):
261
 
        """Copy the file-like object into the target location.
262
 
 
263
 
        This function is not strictly safe to use. It is only meant to
264
 
        be used when you already know that the target does not exist.
265
 
        It is not safe, because it will open and truncate the remote
266
 
        file. So there may be a time when the file has invalid contents.
267
 
 
268
 
        :param relpath: The remote location to put the contents.
269
 
        :param f:       File-like object.
270
 
        :param mode:    Possible access permissions for new file.
271
 
                        None means do not set remote permissions.
272
 
        :param create_parent_dir: If we cannot create the target file because
273
 
                        the parent directory does not exist, go ahead and
274
 
                        create it, and then try again.
275
 
        """
276
 
        def writer(fd):
277
 
            self._pump_to_fd(f, fd)
278
 
        self._put_non_atomic_helper(relpath, writer, mode=mode,
279
 
                                    create_parent_dir=create_parent_dir,
280
 
                                    dir_mode=dir_mode)
281
 
 
282
 
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
283
 
                             create_parent_dir=False, dir_mode=None):
284
 
        def writer(fd):
285
 
            if bytes:
286
 
                os.write(fd, bytes)
287
 
        self._put_non_atomic_helper(relpath, writer, mode=mode,
288
 
                                    create_parent_dir=create_parent_dir,
289
 
                                    dir_mode=dir_mode)
 
85
        try:
 
86
            path = self.abspath(relpath)
 
87
            return open(path, 'rb')
 
88
        except IOError,e:
 
89
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
 
90
                raise NoSuchFile('File or directory %r does not exist' % path, orig_error=e)
 
91
            raise LocalTransportError(orig_error=e)
 
92
 
 
93
    def put(self, relpath, f):
 
94
        """Copy the file-like or string object into the location.
 
95
 
 
96
        :param relpath: Location to put the contents, relative to base.
 
97
        :param f:       File-like or string object.
 
98
        """
 
99
        from bzrlib.atomicfile import AtomicFile
 
100
 
 
101
        try:
 
102
            path = self.abspath(relpath)
 
103
            fp = AtomicFile(path, 'wb')
 
104
        except IOError, e:
 
105
            if e.errno == errno.ENOENT:
 
106
                raise NoSuchFile('File %r does not exist' % path, orig_error=e)
 
107
            raise LocalTransportError(orig_error=e)
 
108
        try:
 
109
            self._pump(f, fp)
 
110
            fp.commit()
 
111
        finally:
 
112
            fp.close()
290
113
 
291
114
    def iter_files_recursive(self):
292
115
        """Iter the relative paths of files in the transports sub-tree."""
293
 
        queue = list(self.list_dir(u'.'))
 
116
        queue = list(self.list_dir('.'))
294
117
        while queue:
295
 
            relpath = queue.pop(0)
 
118
            relpath = urllib.quote(queue.pop(0))
296
119
            st = self.stat(relpath)
297
120
            if S_ISDIR(st[ST_MODE]):
298
121
                for i, basename in enumerate(self.list_dir(relpath)):
299
 
                    queue.insert(i, relpath + '/' + basename)
 
122
                    queue.insert(i, relpath+'/'+basename)
300
123
            else:
301
124
                yield relpath
302
125
 
303
 
    def _mkdir(self, abspath, mode=None):
304
 
        """Create a real directory, filtering through mode"""
305
 
        if mode is None:
306
 
            # os.mkdir() will filter through umask
307
 
            local_mode = 0o777
308
 
        else:
309
 
            local_mode = mode
310
 
        try:
311
 
            os.mkdir(abspath, local_mode)
312
 
        except (IOError, OSError) as e:
313
 
            self._translate_error(e, abspath)
314
 
        if mode is not None:
315
 
            try:
316
 
                osutils.chmod_if_possible(abspath, mode)
317
 
            except (IOError, OSError) as e:
318
 
                self._translate_error(e, abspath)
319
 
 
320
 
    def mkdir(self, relpath, mode=None):
 
126
    def mkdir(self, relpath):
321
127
        """Create a directory at the given path."""
322
 
        self._mkdir(self._abspath(relpath), mode=mode)
323
 
 
324
 
    def open_write_stream(self, relpath, mode=None):
325
 
        """See Transport.open_write_stream."""
326
 
        abspath = self._abspath(relpath)
327
 
        try:
328
 
            handle = osutils.open_file(abspath, 'wb')
329
 
        except (IOError, OSError) as e:
330
 
            self._translate_error(e, abspath)
331
 
        handle.truncate()
332
 
        if mode is not None:
333
 
            self._check_mode_and_size(abspath, handle.fileno(), mode)
334
 
        transport._file_streams[self.abspath(relpath)] = handle
335
 
        return transport.FileFileStream(self, relpath, handle)
336
 
 
337
 
    def _get_append_file(self, relpath, mode=None):
338
 
        """Call os.open() for the given relpath"""
339
 
        file_abspath = self._abspath(relpath)
340
 
        if mode is None:
341
 
            # os.open() will automatically use the umask
342
 
            local_mode = 0o666
343
 
        else:
344
 
            local_mode = mode
345
 
        try:
346
 
            return file_abspath, os.open(file_abspath, _append_flags, local_mode)
347
 
        except (IOError, OSError) as e:
348
 
            self._translate_error(e, relpath)
349
 
 
350
 
    def _check_mode_and_size(self, file_abspath, fd, mode=None):
351
 
        """Check the mode of the file, and return the current size"""
352
 
        st = os.fstat(fd)
353
 
        if mode is not None and mode != S_IMODE(st.st_mode):
354
 
            # Because of umask, we may still need to chmod the file.
355
 
            # But in the general case, we won't have to
356
 
            osutils.chmod_if_possible(file_abspath, mode)
357
 
        return st.st_size
358
 
 
359
 
    def append_file(self, relpath, f, mode=None):
360
 
        """Append the text in the file-like object into the final location."""
361
 
        file_abspath, fd = self._get_append_file(relpath, mode=mode)
362
 
        try:
363
 
            result = self._check_mode_and_size(file_abspath, fd, mode=mode)
364
 
            self._pump_to_fd(f, fd)
365
 
        finally:
366
 
            os.close(fd)
367
 
        return result
368
 
 
369
 
    def append_bytes(self, relpath, bytes, mode=None):
370
 
        """Append the text in the string into the final location."""
371
 
        file_abspath, fd = self._get_append_file(relpath, mode=mode)
372
 
        try:
373
 
            result = self._check_mode_and_size(file_abspath, fd, mode=mode)
374
 
            if bytes:
375
 
                os.write(fd, bytes)
376
 
        finally:
377
 
            os.close(fd)
378
 
        return result
379
 
 
380
 
    def _pump_to_fd(self, fromfile, to_fd):
381
 
        """Copy contents of one file to another."""
382
 
        BUFSIZE = 32768
383
 
        while True:
384
 
            b = fromfile.read(BUFSIZE)
385
 
            if not b:
386
 
                break
387
 
            os.write(to_fd, b)
 
128
        try:
 
129
            os.mkdir(self.abspath(relpath))
 
130
        except OSError,e:
 
131
            if e.errno == errno.EEXIST:
 
132
                raise FileExists(orig_error=e)
 
133
            elif e.errno == errno.ENOENT:
 
134
                raise NoSuchFile(orig_error=e)
 
135
            raise LocalTransportError(orig_error=e)
 
136
 
 
137
    def append(self, relpath, f):
 
138
        """Append the text in the file-like object into the final
 
139
        location.
 
140
        """
 
141
        fp = open(self.abspath(relpath), 'ab')
 
142
        self._pump(f, fp)
388
143
 
389
144
    def copy(self, rel_from, rel_to):
390
145
        """Copy the item at rel_from to the location at rel_to"""
391
 
        path_from = self._abspath(rel_from)
392
 
        path_to = self._abspath(rel_to)
 
146
        import shutil
 
147
        path_from = self.abspath(rel_from)
 
148
        path_to = self.abspath(rel_to)
393
149
        try:
394
150
            shutil.copy(path_from, path_to)
395
 
        except (IOError, OSError) as e:
396
 
            # TODO: What about path_to?
397
 
            self._translate_error(e, path_from)
398
 
 
399
 
    def rename(self, rel_from, rel_to):
400
 
        path_from = self._abspath(rel_from)
401
 
        path_to = self._abspath(rel_to)
402
 
        try:
403
 
            # *don't* call breezy.osutils.rename, because we want to
404
 
            # detect conflicting names on rename, and osutils.rename tries to
405
 
            # mask cross-platform differences there
406
 
            os.rename(path_from, path_to)
407
 
        except (IOError, OSError) as e:
408
 
            # TODO: What about path_to?
409
 
            self._translate_error(e, path_from)
 
151
        except OSError,e:
 
152
            raise LocalTransportError(orig_error=e)
410
153
 
411
154
    def move(self, rel_from, rel_to):
412
155
        """Move the item at rel_from to the location at rel_to"""
413
 
        path_from = self._abspath(rel_from)
414
 
        path_to = self._abspath(rel_to)
 
156
        path_from = self.abspath(rel_from)
 
157
        path_to = self.abspath(rel_to)
415
158
 
416
159
        try:
417
 
            # this version will delete the destination if necessary
418
 
            osutils.rename(path_from, path_to)
419
 
        except (IOError, OSError) as e:
420
 
            # TODO: What about path_to?
421
 
            self._translate_error(e, path_from)
 
160
            os.rename(path_from, path_to)
 
161
        except OSError,e:
 
162
            raise LocalTransportError(orig_error=e)
422
163
 
423
164
    def delete(self, relpath):
424
165
        """Delete the item at relpath"""
425
 
        path = relpath
426
166
        try:
427
 
            path = self._abspath(relpath)
428
 
            os.remove(path)
429
 
        except (IOError, OSError) as e:
430
 
            self._translate_error(e, path)
431
 
 
432
 
    def external_url(self):
433
 
        """See breezy.transport.Transport.external_url."""
434
 
        # File URL's are externally usable.
435
 
        return self.base
436
 
 
437
 
    def copy_to(self, relpaths, other, mode=None, pb=None):
 
167
            os.remove(self.abspath(relpath))
 
168
        except OSError,e:
 
169
            raise LocalTransportError(orig_error=e)
 
170
 
 
171
    def copy_to(self, relpaths, other, pb=None):
438
172
        """Copy a set of entries from self into another Transport.
439
173
 
440
174
        :param relpaths: A list/generator of entries to be copied.
443
177
            # Both from & to are on the local filesystem
444
178
            # Unfortunately, I can't think of anything faster than just
445
179
            # copying them across, one by one :(
 
180
            import shutil
 
181
 
446
182
            total = self._get_total(relpaths)
447
183
            count = 0
448
184
            for path in relpaths:
449
185
                self._update_pb(pb, 'copy-to', count, total)
450
186
                try:
451
 
                    mypath = self._abspath(path)
452
 
                    otherpath = other._abspath(path)
453
 
                    shutil.copy(mypath, otherpath)
454
 
                    if mode is not None:
455
 
                        osutils.chmod_if_possible(otherpath, mode)
456
 
                except (IOError, OSError) as e:
457
 
                    self._translate_error(e, path)
 
187
                    shutil.copy(self.abspath(path), other.abspath(path))
 
188
                except IOError, e:
 
189
                    if e.errno in (errno.ENOENT, errno.ENOTDIR):
 
190
                        raise NoSuchFile('File or directory %r does not exist' % path, orig_error=e)
 
191
                    raise LocalTransportError(orig_error=e)
458
192
                count += 1
459
193
            return count
460
194
        else:
461
 
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
 
195
            return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
462
196
 
463
197
    def listable(self):
464
198
        """See Transport.listable."""
469
203
        WARNING: many transports do not support this, so trying avoid using
470
204
        it if at all possible.
471
205
        """
472
 
        path = self._abspath(relpath)
473
206
        try:
474
 
            entries = os.listdir(path)
475
 
        except (IOError, OSError) as e:
476
 
            self._translate_error(e, path)
477
 
        return [urlutils.escape(entry) for entry in entries]
 
207
            return os.listdir(self.abspath(relpath))
 
208
        except OSError,e:
 
209
            raise LocalTransportError(orig_error=e)
478
210
 
479
211
    def stat(self, relpath):
480
212
        """Return the stat information for a file.
481
213
        """
482
 
        path = relpath
483
214
        try:
484
 
            path = self._abspath(relpath)
485
 
            return os.lstat(path)
486
 
        except (IOError, OSError) as e:
487
 
            self._translate_error(e, path)
 
215
            return os.stat(self.abspath(relpath))
 
216
        except OSError,e:
 
217
            raise LocalTransportError(orig_error=e)
488
218
 
489
219
    def lock_read(self, relpath):
490
220
        """Lock the given file for shared (read) access.
491
221
        :return: A lock object, which should be passed to Transport.unlock()
492
222
        """
493
 
        from breezy.lock import ReadLock
494
 
        path = relpath
495
 
        try:
496
 
            path = self._abspath(relpath)
497
 
            return ReadLock(path)
498
 
        except (IOError, OSError) as e:
499
 
            self._translate_error(e, path)
 
223
        from bzrlib.lock import ReadLock
 
224
        return ReadLock(self.abspath(relpath))
500
225
 
501
226
    def lock_write(self, relpath):
502
227
        """Lock the given file for exclusive (write) access.
504
229
 
505
230
        :return: A lock object, which should be passed to Transport.unlock()
506
231
        """
507
 
        from breezy.lock import WriteLock
508
 
        return WriteLock(self._abspath(relpath))
509
 
 
510
 
    def rmdir(self, relpath):
511
 
        """See Transport.rmdir."""
512
 
        path = relpath
513
 
        try:
514
 
            path = self._abspath(relpath)
515
 
            os.rmdir(path)
516
 
        except (IOError, OSError) as e:
517
 
            self._translate_error(e, path)
518
 
 
519
 
    if osutils.host_os_dereferences_symlinks():
520
 
        def readlink(self, relpath):
521
 
            """See Transport.readlink."""
522
 
            try:
523
 
                return osutils.readlink(self._abspath(relpath))
524
 
            except (IOError, OSError) as e:
525
 
                self._translate_error(e, relpath)
526
 
 
527
 
    if osutils.hardlinks_good():
528
 
        def hardlink(self, source, link_name):
529
 
            """See Transport.link."""
530
 
            try:
531
 
                os.link(self._abspath(source), self._abspath(link_name))
532
 
            except (IOError, OSError) as e:
533
 
                self._translate_error(e, source)
534
 
 
535
 
    if getattr(os, 'symlink', None) is not None:
536
 
        def symlink(self, source, link_name):
537
 
            """See Transport.symlink."""
538
 
            abs_link_dirpath = urlutils.dirname(self.abspath(link_name))
539
 
            source_rel = urlutils.file_relpath(
540
 
                abs_link_dirpath, self.abspath(source))
541
 
 
542
 
            try:
543
 
                os.symlink(source_rel, self._abspath(link_name))
544
 
            except (IOError, OSError) as e:
545
 
                self._translate_error(e, source_rel)
546
 
 
547
 
    def _can_roundtrip_unix_modebits(self):
548
 
        if sys.platform == 'win32':
549
 
            # anyone else?
550
 
            return False
551
 
        else:
552
 
            return True
553
 
 
554
 
 
555
 
class EmulatedWin32LocalTransport(LocalTransport):
556
 
    """Special transport for testing Win32 [UNC] paths on non-windows"""
557
 
 
558
 
    def __init__(self, base):
559
 
        if base[-1] != '/':
560
 
            base = base + '/'
561
 
        super(LocalTransport, self).__init__(base)
562
 
        self._local_base = urlutils._win32_local_path_from_url(base)
563
 
 
564
 
    def abspath(self, relpath):
565
 
        path = osutils._win32_normpath(osutils.pathjoin(
566
 
            self._local_base, urlutils.unescape(relpath)))
567
 
        return urlutils._win32_local_path_to_url(path)
568
 
 
569
 
    def clone(self, offset=None):
570
 
        """Return a new LocalTransport with root at self.base + offset
571
 
        Because the local filesystem does not require a connection,
572
 
        we can just return a new object.
573
 
        """
574
 
        if offset is None:
575
 
            return EmulatedWin32LocalTransport(self.base)
576
 
        else:
577
 
            abspath = self.abspath(offset)
578
 
            if abspath == 'file://':
579
 
                # fix upwalk for UNC path
580
 
                # when clone from //HOST/path updir recursively
581
 
                # we should stop at least at //HOST part
582
 
                abspath = self.base
583
 
            return EmulatedWin32LocalTransport(abspath)
584
 
 
585
 
 
586
 
def get_test_permutations():
587
 
    """Return the permutations to be used in testing."""
588
 
    from ..tests import test_server
589
 
    return [(LocalTransport, test_server.LocalURLServer), ]
 
232
        from bzrlib.lock import WriteLock
 
233
        return WriteLock(self.abspath(relpath))
 
234
 
 
235
 
 
236
class ScratchTransport(LocalTransport):
 
237
    """A transport that works in a temporary dir and cleans up after itself.
 
238
    
 
239
    The dir only exists for the lifetime of the Python object.
 
240
    Obviously you should not put anything precious in it.
 
241
    """
 
242
 
 
243
    def __init__(self, base=None):
 
244
        if base is None:
 
245
            base = tempfile.mkdtemp()
 
246
        super(ScratchTransport, self).__init__(base)
 
247
 
 
248
    def __del__(self):
 
249
        shutil.rmtree(self.base, ignore_errors=True)
 
250
        mutter("%r destroyed" % self)