/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

  • Committer: Robert Collins
  • Date: 2010-05-11 08:44:59 UTC
  • mfrom: (5221 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100511084459-pb0uinna9zs3wu59
Merge trunk - resolve conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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
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
import os
 
23
from stat import ST_MODE, S_ISDIR, ST_SIZE, S_IMODE
 
24
import sys
 
25
 
 
26
from bzrlib.lazy_import import lazy_import
 
27
lazy_import(globals(), """
22
28
import errno
23
 
import os
24
29
import shutil
25
 
import sys
26
 
from stat import ST_MODE, S_ISDIR, ST_SIZE, S_IMODE
27
 
import tempfile
28
30
 
29
31
from bzrlib import (
30
32
    atomicfile,
31
33
    osutils,
32
34
    urlutils,
 
35
    symbol_versioning,
 
36
    transport,
33
37
    )
34
 
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename,
35
 
                            check_legal_path, rmtree)
36
 
from bzrlib.symbol_versioning import warn
37
38
from bzrlib.trace import mutter
38
 
from bzrlib.transport import Transport, Server
39
 
 
40
 
 
41
 
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY
42
 
_put_non_atomic_flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | osutils.O_BINARY
43
 
 
44
 
 
45
 
class LocalTransport(Transport):
 
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):
46
50
    """This is the transport agent for local filesystem access."""
47
51
 
48
52
    def __init__(self, base):
49
53
        """Set the base path where files will be stored."""
50
54
        if not base.startswith('file://'):
51
 
            warn("Instantiating LocalTransport with a filesystem path"
 
55
            symbol_versioning.warn(
 
56
                "Instantiating LocalTransport with a filesystem path"
52
57
                " is deprecated as of bzr 0.8."
53
58
                " Please use bzrlib.transport.get_transport()"
54
59
                " or pass in a file:// url.",
58
63
            base = urlutils.local_path_to_url(base)
59
64
        if base[-1] != '/':
60
65
            base = base + '/'
 
66
 
 
67
        # Special case : windows has no "root", but does have
 
68
        # multiple lettered drives inside it. #240910
 
69
        if sys.platform == 'win32' and base == 'file:///':
 
70
            base = ''
 
71
            self._local_base = ''
 
72
            super(LocalTransport, self).__init__(base)
 
73
            return
 
74
 
61
75
        super(LocalTransport, self).__init__(base)
62
76
        self._local_base = urlutils.local_path_from_url(base)
63
77
 
64
 
    def should_cache(self):
65
 
        return False
66
 
 
67
78
    def clone(self, offset=None):
68
79
        """Return a new LocalTransport with root at self.base + offset
69
 
        Because the local filesystem does not require a connection, 
 
80
        Because the local filesystem does not require a connection,
70
81
        we can just return a new object.
71
82
        """
72
83
        if offset is None:
73
84
            return LocalTransport(self.base)
74
85
        else:
75
 
            return LocalTransport(self.abspath(offset))
 
86
            abspath = self.abspath(offset)
 
87
            if abspath == 'file://':
 
88
                # fix upwalk for UNC path
 
89
                # when clone from //HOST/path updir recursively
 
90
                # we should stop at least at //HOST part
 
91
                abspath = self.base
 
92
            return LocalTransport(abspath)
76
93
 
77
94
    def _abspath(self, relative_reference):
78
95
        """Return a path for use in os calls.
88
105
    def abspath(self, relpath):
89
106
        """Return the full url to the given relative URL."""
90
107
        # TODO: url escape the result. RBC 20060523.
91
 
        assert isinstance(relpath, basestring), (type(relpath), relpath)
92
108
        # jam 20060426 Using normpath on the real path, because that ensures
93
109
        #       proper handling of stuff like
94
 
        path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
 
110
        path = osutils.normpath(osutils.pathjoin(
 
111
                    self._local_base, urlutils.unescape(relpath)))
 
112
        # on windows, our _local_base may or may not have a drive specified
 
113
        # (ie, it may be "/" or "c:/foo").
 
114
        # If 'relpath' is '/' we *always* get back an abspath without
 
115
        # the drive letter - but if our transport already has a drive letter,
 
116
        # we want our abspaths to have a drive letter too - so handle that
 
117
        # here.
 
118
        if (sys.platform == "win32" and self._local_base[1:2] == ":"
 
119
            and path == '/'):
 
120
            path = self._local_base[:3]
 
121
 
95
122
        return urlutils.local_path_to_url(path)
96
123
 
97
124
    def local_abspath(self, relpath):
100
127
        This function only exists for the LocalTransport, since it is
101
128
        the only one that has direct local access.
102
129
        This is mostly for stuff like WorkingTree which needs to know
103
 
        the local working directory.
104
 
        
 
130
        the local working directory.  The returned path will always contain
 
131
        forward slashes as the path separator, regardless of the platform.
 
132
 
105
133
        This function is quite expensive: it calls realpath which resolves
106
134
        symlinks.
107
135
        """
116
144
            abspath = u'.'
117
145
 
118
146
        return urlutils.file_relpath(
119
 
            urlutils.strip_trailing_slash(self.base), 
 
147
            urlutils.strip_trailing_slash(self.base),
120
148
            urlutils.strip_trailing_slash(abspath))
121
149
 
122
150
    def has(self, relpath):
127
155
 
128
156
        :param relpath: The relative path to the file
129
157
        """
 
158
        canonical_url = self.abspath(relpath)
 
159
        if canonical_url in transport._file_streams:
 
160
            transport._file_streams[canonical_url].flush()
130
161
        try:
131
162
            path = self._abspath(relpath)
132
 
            return open(path, 'rb')
 
163
            return osutils.open_file(path, 'rb')
133
164
        except (IOError, OSError),e:
 
165
            if e.errno == errno.EISDIR:
 
166
                return LateReadError(relpath)
134
167
            self._translate_error(e, path)
135
168
 
136
169
    def put_file(self, relpath, f, mode=None):
138
171
 
139
172
        :param relpath: Location to put the contents, relative to base.
140
173
        :param f:       File-like object.
141
 
        :param mode: The mode for the newly created file, 
 
174
        :param mode: The mode for the newly created file,
142
175
                     None means just use the default
143
176
        """
144
177
 
145
178
        path = relpath
146
179
        try:
147
180
            path = self._abspath(relpath)
148
 
            check_legal_path(path)
 
181
            osutils.check_legal_path(path)
149
182
            fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
150
183
        except (IOError, OSError),e:
151
184
            self._translate_error(e, path)
152
185
        try:
153
 
            self._pump(f, fp)
 
186
            length = self._pump(f, fp)
154
187
            fp.commit()
155
188
        finally:
156
189
            fp.close()
 
190
        return length
157
191
 
158
192
    def put_bytes(self, relpath, bytes, mode=None):
159
193
        """Copy the string into the location.
165
199
        path = relpath
166
200
        try:
167
201
            path = self._abspath(relpath)
168
 
            check_legal_path(path)
 
202
            osutils.check_legal_path(path)
169
203
            fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
170
204
        except (IOError, OSError),e:
171
205
            self._translate_error(e, path)
172
206
        try:
173
 
            fp.write(bytes)
 
207
            if bytes:
 
208
                fp.write(bytes)
174
209
            fp.commit()
175
210
        finally:
176
211
            fp.close()
251
286
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
252
287
                             create_parent_dir=False, dir_mode=None):
253
288
        def writer(fd):
254
 
            os.write(fd, bytes)
 
289
            if bytes:
 
290
                os.write(fd, bytes)
255
291
        self._put_non_atomic_helper(relpath, writer, mode=mode,
256
292
                                    create_parent_dir=create_parent_dir,
257
293
                                    dir_mode=dir_mode)
288
324
        """Create a directory at the given path."""
289
325
        self._mkdir(self._abspath(relpath), mode=mode)
290
326
 
 
327
    def open_write_stream(self, relpath, mode=None):
 
328
        """See Transport.open_write_stream."""
 
329
        # initialise the file
 
330
        self.put_bytes_non_atomic(relpath, "", mode=mode)
 
331
        abspath = self._abspath(relpath)
 
332
        handle = osutils.open_file(abspath, 'wb')
 
333
        if mode is not None:
 
334
            self._check_mode_and_size(abspath, handle.fileno(), mode)
 
335
        transport._file_streams[self.abspath(relpath)] = handle
 
336
        return transport.FileFileStream(self, relpath, handle)
 
337
 
291
338
    def _get_append_file(self, relpath, mode=None):
292
339
        """Call os.open() for the given relpath"""
293
340
        file_abspath = self._abspath(relpath)
325
372
        file_abspath, fd = self._get_append_file(relpath, mode=mode)
326
373
        try:
327
374
            result = self._check_mode_and_size(file_abspath, fd, mode=mode)
328
 
            os.write(fd, bytes)
 
375
            if bytes:
 
376
                os.write(fd, bytes)
329
377
        finally:
330
378
            os.close(fd)
331
379
        return result
351
399
 
352
400
    def rename(self, rel_from, rel_to):
353
401
        path_from = self._abspath(rel_from)
 
402
        path_to = self._abspath(rel_to)
354
403
        try:
355
 
            # *don't* call bzrlib.osutils.rename, because we want to 
356
 
            # detect errors on rename
357
 
            os.rename(path_from, self._abspath(rel_to))
 
404
            # *don't* call bzrlib.osutils.rename, because we want to
 
405
            # detect conflicting names on rename, and osutils.rename tries to
 
406
            # mask cross-platform differences there; however we do update the
 
407
            # exception to include the filenames
 
408
            os.rename(path_from, path_to)
358
409
        except (IOError, OSError),e:
359
410
            # TODO: What about path_to?
360
 
            self._translate_error(e, path_from)
 
411
            self._translate_error(
 
412
                osutils._add_rename_error_details(e, path_from, path_to),
 
413
                path_from)
361
414
 
362
415
    def move(self, rel_from, rel_to):
363
416
        """Move the item at rel_from to the location at rel_to"""
366
419
 
367
420
        try:
368
421
            # this version will delete the destination if necessary
369
 
            rename(path_from, path_to)
 
422
            osutils.rename(path_from, path_to)
370
423
        except (IOError, OSError),e:
371
424
            # TODO: What about path_to?
372
425
            self._translate_error(e, path_from)
380
433
        except (IOError, OSError),e:
381
434
            self._translate_error(e, path)
382
435
 
 
436
    def external_url(self):
 
437
        """See bzrlib.transport.Transport.external_url."""
 
438
        # File URL's are externally usable.
 
439
        return self.base
 
440
 
383
441
    def copy_to(self, relpaths, other, mode=None, pb=None):
384
442
        """Copy a set of entries from self into another Transport.
385
443
 
428
486
        path = relpath
429
487
        try:
430
488
            path = self._abspath(relpath)
431
 
            return os.stat(path)
 
489
            return os.lstat(path)
432
490
        except (IOError, OSError),e:
433
491
            self._translate_error(e, path)
434
492
 
462
520
        except (IOError, OSError),e:
463
521
            self._translate_error(e, path)
464
522
 
 
523
    if osutils.host_os_dereferences_symlinks():
 
524
        def readlink(self, relpath):
 
525
            """See Transport.readlink."""
 
526
            return osutils.readlink(self._abspath(relpath))
 
527
 
 
528
    if osutils.hardlinks_good():
 
529
        def hardlink(self, source, link_name):
 
530
            """See Transport.link."""
 
531
            try:
 
532
                os.link(self._abspath(source), self._abspath(link_name))
 
533
            except (IOError, OSError), e:
 
534
                self._translate_error(e, source)
 
535
 
 
536
    if osutils.has_symlinks():
 
537
        def symlink(self, source, link_name):
 
538
            """See Transport.symlink."""
 
539
            abs_link_dirpath = urlutils.dirname(self.abspath(link_name))
 
540
            source_rel = urlutils.file_relpath(
 
541
                urlutils.strip_trailing_slash(abs_link_dirpath),
 
542
                urlutils.strip_trailing_slash(self.abspath(source))
 
543
            )
 
544
 
 
545
            try:
 
546
                os.symlink(source_rel, self._abspath(link_name))
 
547
            except (IOError, OSError), e:
 
548
                self._translate_error(e, source_rel)
 
549
 
465
550
    def _can_roundtrip_unix_modebits(self):
466
551
        if sys.platform == 'win32':
467
552
            # anyone else?
470
555
            return True
471
556
 
472
557
 
473
 
class LocalRelpathServer(Server):
474
 
    """A pretend server for local transports, using relpaths."""
475
 
 
476
 
    def get_url(self):
477
 
        """See Transport.Server.get_url."""
478
 
        return "."
479
 
 
480
 
 
481
 
class LocalAbspathServer(Server):
482
 
    """A pretend server for local transports, using absolute paths."""
483
 
 
484
 
    def get_url(self):
485
 
        """See Transport.Server.get_url."""
486
 
        return os.path.abspath("")
487
 
 
488
 
 
489
 
class LocalURLServer(Server):
490
 
    """A pretend server for local transports, using file:// urls."""
491
 
 
492
 
    def get_url(self):
493
 
        """See Transport.Server.get_url."""
494
 
        return urlutils.local_path_to_url('')
 
558
class EmulatedWin32LocalTransport(LocalTransport):
 
559
    """Special transport for testing Win32 [UNC] paths on non-windows"""
 
560
 
 
561
    def __init__(self, base):
 
562
        if base[-1] != '/':
 
563
            base = base + '/'
 
564
        super(LocalTransport, self).__init__(base)
 
565
        self._local_base = urlutils._win32_local_path_from_url(base)
 
566
 
 
567
    def abspath(self, relpath):
 
568
        path = osutils.normpath(osutils.pathjoin(
 
569
                    self._local_base, urlutils.unescape(relpath)))
 
570
        return urlutils._win32_local_path_to_url(path)
 
571
 
 
572
    def clone(self, offset=None):
 
573
        """Return a new LocalTransport with root at self.base + offset
 
574
        Because the local filesystem does not require a connection,
 
575
        we can just return a new object.
 
576
        """
 
577
        if offset is None:
 
578
            return EmulatedWin32LocalTransport(self.base)
 
579
        else:
 
580
            abspath = self.abspath(offset)
 
581
            if abspath == 'file://':
 
582
                # fix upwalk for UNC path
 
583
                # when clone from //HOST/path updir recursively
 
584
                # we should stop at least at //HOST part
 
585
                abspath = self.base
 
586
            return EmulatedWin32LocalTransport(abspath)
495
587
 
496
588
 
497
589
def get_test_permutations():
498
590
    """Return the permutations to be used in testing."""
499
 
    return [(LocalTransport, LocalRelpathServer),
500
 
            (LocalTransport, LocalAbspathServer),
501
 
            (LocalTransport, LocalURLServer),
502
 
            ]
 
591
    from bzrlib.tests import test_server
 
592
    return [(LocalTransport, test_server.LocalURLServer),]