~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

(vila) Fix test failures blocking package builds. (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

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