~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Patch Queue Manager
  • Date: 2012-10-25 11:13:27 UTC
  • mfrom: (6570.1.6 rubberstamp)
  • Revision ID: pqm@pqm.ubuntu.com-20121025111327-p0ylql0nh9fla0rs
(gz) Set approved revision and vote "Approve" when using lp-propose
 --approve (Jonathan Lange)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 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
19
This is a fairly thin wrapper on regular file IO.
20
20
"""
21
21
 
 
22
from __future__ import absolute_import
 
23
 
22
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
23
31
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
 
 
35
 
 
36
 
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):
37
50
    """This is the transport agent for local filesystem access."""
38
51
 
39
52
    def __init__(self, base):
40
53
        """Set the base path where files will be stored."""
41
54
        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)
 
55
            raise AssertionError("not a file:// url: %r" % base)
50
56
        if base[-1] != '/':
51
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
 
52
67
        super(LocalTransport, self).__init__(base)
53
68
        self._local_base = urlutils.local_path_from_url(base)
54
 
 
55
 
    def should_cache(self):
56
 
        return False
 
69
        if self._local_base[-1] != '/':
 
70
            self._local_base = self._local_base + '/'
57
71
 
58
72
    def clone(self, offset=None):
59
73
        """Return a new LocalTransport with root at self.base + offset
60
 
        Because the local filesystem does not require a connection, 
 
74
        Because the local filesystem does not require a connection,
61
75
        we can just return a new object.
62
76
        """
63
77
        if offset is None:
64
78
            return LocalTransport(self.base)
65
79
        else:
66
 
            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)
67
87
 
68
88
    def _abspath(self, relative_reference):
69
89
        """Return a path for use in os calls.
73
93
         - relative_reference is url escaped.
74
94
        """
75
95
        if relative_reference in ('.', ''):
76
 
            return self._local_base
 
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]
77
100
        return self._local_base + urlutils.unescape(relative_reference)
78
101
 
79
102
    def abspath(self, relpath):
80
103
        """Return the full url to the given relative URL."""
81
104
        # TODO: url escape the result. RBC 20060523.
82
 
        assert isinstance(relpath, basestring), (type(relpath), relpath)
83
105
        # jam 20060426 Using normpath on the real path, because that ensures
84
106
        #       proper handling of stuff like
85
 
        path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
 
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
 
86
119
        return urlutils.local_path_to_url(path)
87
120
 
88
121
    def local_abspath(self, relpath):
91
124
        This function only exists for the LocalTransport, since it is
92
125
        the only one that has direct local access.
93
126
        This is mostly for stuff like WorkingTree which needs to know
94
 
        the local working directory.
95
 
        
 
127
        the local working directory.  The returned path will always contain
 
128
        forward slashes as the path separator, regardless of the platform.
 
129
 
96
130
        This function is quite expensive: it calls realpath which resolves
97
131
        symlinks.
98
132
        """
106
140
        if abspath is None:
107
141
            abspath = u'.'
108
142
 
109
 
        return urlutils.file_relpath(
110
 
            urlutils.strip_trailing_slash(self.base), 
111
 
            urlutils.strip_trailing_slash(abspath))
 
143
        return urlutils.file_relpath(self.base, abspath)
112
144
 
113
145
    def has(self, relpath):
114
146
        return os.access(self._abspath(relpath), os.F_OK)
118
150
 
119
151
        :param relpath: The relative path to the file
120
152
        """
121
 
        try:
122
 
            path = self._abspath(relpath)
123
 
            return open(path, 'rb')
124
 
        except (IOError, OSError),e:
125
 
            self._translate_error(e, path)
126
 
 
127
 
    def put(self, relpath, f, mode=None):
128
 
        """Copy the file-like or string object into the location.
129
 
 
130
 
        :param relpath: Location to put the contents, relative to base.
131
 
        :param f:       File-like or string object.
132
 
        """
133
 
        from bzrlib.atomicfile import AtomicFile
134
 
 
135
 
        path = relpath
136
 
        try:
137
 
            path = self._abspath(relpath)
138
 
            check_legal_path(path)
139
 
            fp = AtomicFile(path, 'wb', new_mode=mode)
140
 
        except (IOError, OSError),e:
141
 
            self._translate_error(e, path)
142
 
        try:
143
 
            self._pump(f, fp)
144
 
            fp.commit()
145
 
        finally:
146
 
            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)
147
289
 
148
290
    def iter_files_recursive(self):
149
291
        """Iter the relative paths of files in the transports sub-tree."""
157
299
            else:
158
300
                yield relpath
159
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
 
160
319
    def mkdir(self, relpath, mode=None):
161
320
        """Create a directory at the given path."""
162
 
        path = relpath
163
 
        try:
164
 
            path = self._abspath(relpath)
165
 
            os.mkdir(path)
166
 
            if mode is not None:
167
 
                os.chmod(path, mode)
168
 
        except (IOError, OSError),e:
169
 
            self._translate_error(e, path)
170
 
 
171
 
    def append(self, relpath, f, mode=None):
 
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):
172
359
        """Append the text in the file-like object into the final location."""
173
 
        abspath = self._abspath(relpath)
174
 
        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)
179
 
        except (IOError, OSError),e:
180
 
            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
 
        self._pump(f, fp)
185
 
        return result
 
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)
186
387
 
187
388
    def copy(self, rel_from, rel_to):
188
389
        """Copy the item at rel_from to the location at rel_to"""
196
397
 
197
398
    def rename(self, rel_from, rel_to):
198
399
        path_from = self._abspath(rel_from)
 
400
        path_to = self._abspath(rel_to)
199
401
        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))
 
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)
203
406
        except (IOError, OSError),e:
204
407
            # TODO: What about path_to?
205
408
            self._translate_error(e, path_from)
211
414
 
212
415
        try:
213
416
            # this version will delete the destination if necessary
214
 
            rename(path_from, path_to)
 
417
            osutils.rename(path_from, path_to)
215
418
        except (IOError, OSError),e:
216
419
            # TODO: What about path_to?
217
420
            self._translate_error(e, path_from)
225
428
        except (IOError, OSError),e:
226
429
            self._translate_error(e, path)
227
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
 
228
436
    def copy_to(self, relpaths, other, mode=None, pb=None):
229
437
        """Copy a set of entries from self into another Transport.
230
438
 
243
451
                    otherpath = other._abspath(path)
244
452
                    shutil.copy(mypath, otherpath)
245
453
                    if mode is not None:
246
 
                        os.chmod(otherpath, mode)
 
454
                        osutils.chmod_if_possible(otherpath, mode)
247
455
                except (IOError, OSError),e:
248
456
                    self._translate_error(e, path)
249
457
                count += 1
262
470
        """
263
471
        path = self._abspath(relpath)
264
472
        try:
265
 
            return [urlutils.escape(entry) for entry in os.listdir(path)]
 
473
            entries = os.listdir(path)
266
474
        except (IOError, OSError), e:
267
475
            self._translate_error(e, path)
 
476
        return [urlutils.escape(entry) for entry in entries]
268
477
 
269
478
    def stat(self, relpath):
270
479
        """Return the stat information for a file.
272
481
        path = relpath
273
482
        try:
274
483
            path = self._abspath(relpath)
275
 
            return os.stat(path)
 
484
            return os.lstat(path)
276
485
        except (IOError, OSError),e:
277
486
            self._translate_error(e, path)
278
487
 
306
515
        except (IOError, OSError),e:
307
516
            self._translate_error(e, path)
308
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
 
309
543
    def _can_roundtrip_unix_modebits(self):
310
544
        if sys.platform == 'win32':
311
545
            # anyone else?
314
548
            return True
315
549
 
316
550
 
317
 
class ScratchTransport(LocalTransport):
318
 
    """A transport that works in a temporary dir and cleans up after itself.
319
 
    
320
 
    The dir only exists for the lifetime of the Python object.
321
 
    Obviously you should not put anything precious in it.
322
 
    """
323
 
 
324
 
    def __init__(self, base=None):
325
 
        if base is None:
326
 
            base = tempfile.mkdtemp()
327
 
        super(ScratchTransport, self).__init__(base)
328
 
 
329
 
    def __del__(self):
330
 
        rmtree(self.base, ignore_errors=True)
331
 
        mutter("%r destroyed" % self)
332
 
 
333
 
 
334
 
class LocalRelpathServer(Server):
335
 
    """A pretend server for local transports, using relpaths."""
336
 
 
337
 
    def get_url(self):
338
 
        """See Transport.Server.get_url."""
339
 
        return "."
340
 
 
341
 
 
342
 
class LocalAbspathServer(Server):
343
 
    """A pretend server for local transports, using absolute paths."""
344
 
 
345
 
    def get_url(self):
346
 
        """See Transport.Server.get_url."""
347
 
        return os.path.abspath("")
348
 
 
349
 
 
350
 
class LocalURLServer(Server):
351
 
    """A pretend server for local transports, using file:// urls."""
352
 
 
353
 
    def get_url(self):
354
 
        """See Transport.Server.get_url."""
355
 
        return urlutils.local_path_to_url('')
 
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)
356
580
 
357
581
 
358
582
def get_test_permutations():
359
583
    """Return the permutations to be used in testing."""
360
 
    return [(LocalTransport, LocalRelpathServer),
361
 
            (LocalTransport, LocalAbspathServer),
362
 
            (LocalTransport, LocalURLServer),
363
 
            ]
 
584
    from bzrlib.tests import test_server
 
585
    return [(LocalTransport, test_server.LocalURLServer),]