~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: John Arbash Meinel
  • Date: 2013-06-24 12:03:12 UTC
  • mfrom: (6437.77.2 2.5)
  • mto: This revision was merged to the branch mainline in revision 6579.
  • Revision ID: john@arbash-meinel.com-20130624120312-pmvck24x052csigx
Merge lp:bzr/2.5 r6515 to get the fix for bug #855155 (Dirstate.update_basis_by_delta)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
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
 
16
 
 
17
"""Transport for the local filesystem.
 
18
 
 
19
This is a fairly thin wrapper on regular file IO.
 
20
"""
 
21
 
 
22
from __future__ import absolute_import
 
23
 
 
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
 
31
import shutil
 
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):
 
50
    """This is the transport agent for local filesystem access."""
 
51
 
 
52
    def __init__(self, base):
 
53
        """Set the base path where files will be stored."""
 
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 + '/'
 
71
 
 
72
    def clone(self, offset=None):
 
73
        """Return a new LocalTransport with root at self.base + offset
 
74
        Because the local filesystem does not require a connection,
 
75
        we can just return a new object.
 
76
        """
 
77
        if offset is None:
 
78
            return LocalTransport(self.base)
 
79
        else:
 
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)
 
101
 
 
102
    def abspath(self, relpath):
 
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.
 
132
        """
 
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)
 
136
 
 
137
    def relpath(self, abspath):
 
138
        """Return the local path portion from a given absolute path.
 
139
        """
 
140
        if abspath is None:
 
141
            abspath = u'.'
 
142
 
 
143
        return urlutils.file_relpath(self.base, abspath)
 
144
 
 
145
    def has(self, relpath):
 
146
        return os.access(self._abspath(relpath), os.F_OK)
 
147
 
 
148
    def get(self, relpath):
 
149
        """Get the file at the given relative path.
 
150
 
 
151
        :param relpath: The relative path to the file
 
152
        """
 
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)
 
289
 
 
290
    def iter_files_recursive(self):
 
291
        """Iter the relative paths of files in the transports sub-tree."""
 
292
        queue = list(self.list_dir(u'.'))
 
293
        while queue:
 
294
            relpath = queue.pop(0)
 
295
            st = self.stat(relpath)
 
296
            if S_ISDIR(st[ST_MODE]):
 
297
                for i, basename in enumerate(self.list_dir(relpath)):
 
298
                    queue.insert(i, relpath+'/'+basename)
 
299
            else:
 
300
                yield relpath
 
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
 
 
319
    def mkdir(self, relpath, mode=None):
 
320
        """Create a directory at the given path."""
 
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)
 
387
 
 
388
    def copy(self, rel_from, rel_to):
 
389
        """Copy the item at rel_from to the location at rel_to"""
 
390
        path_from = self._abspath(rel_from)
 
391
        path_to = self._abspath(rel_to)
 
392
        try:
 
393
            shutil.copy(path_from, path_to)
 
394
        except (IOError, OSError),e:
 
395
            # TODO: What about path_to?
 
396
            self._translate_error(e, path_from)
 
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
 
 
410
    def move(self, rel_from, rel_to):
 
411
        """Move the item at rel_from to the location at rel_to"""
 
412
        path_from = self._abspath(rel_from)
 
413
        path_to = self._abspath(rel_to)
 
414
 
 
415
        try:
 
416
            # this version will delete the destination if necessary
 
417
            osutils.rename(path_from, path_to)
 
418
        except (IOError, OSError),e:
 
419
            # TODO: What about path_to?
 
420
            self._translate_error(e, path_from)
 
421
 
 
422
    def delete(self, relpath):
 
423
        """Delete the item at relpath"""
 
424
        path = relpath
 
425
        try:
 
426
            path = self._abspath(relpath)
 
427
            os.remove(path)
 
428
        except (IOError, OSError),e:
 
429
            self._translate_error(e, path)
 
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
 
 
436
    def copy_to(self, relpaths, other, mode=None, pb=None):
 
437
        """Copy a set of entries from self into another Transport.
 
438
 
 
439
        :param relpaths: A list/generator of entries to be copied.
 
440
        """
 
441
        if isinstance(other, LocalTransport):
 
442
            # Both from & to are on the local filesystem
 
443
            # Unfortunately, I can't think of anything faster than just
 
444
            # copying them across, one by one :(
 
445
            total = self._get_total(relpaths)
 
446
            count = 0
 
447
            for path in relpaths:
 
448
                self._update_pb(pb, 'copy-to', count, total)
 
449
                try:
 
450
                    mypath = self._abspath(path)
 
451
                    otherpath = other._abspath(path)
 
452
                    shutil.copy(mypath, otherpath)
 
453
                    if mode is not None:
 
454
                        osutils.chmod_if_possible(otherpath, mode)
 
455
                except (IOError, OSError),e:
 
456
                    self._translate_error(e, path)
 
457
                count += 1
 
458
            return count
 
459
        else:
 
460
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
 
461
 
 
462
    def listable(self):
 
463
        """See Transport.listable."""
 
464
        return True
 
465
 
 
466
    def list_dir(self, relpath):
 
467
        """Return a list of all files at the given location.
 
468
        WARNING: many transports do not support this, so trying avoid using
 
469
        it if at all possible.
 
470
        """
 
471
        path = self._abspath(relpath)
 
472
        try:
 
473
            entries = os.listdir(path)
 
474
        except (IOError, OSError), e:
 
475
            self._translate_error(e, path)
 
476
        return [urlutils.escape(entry) for entry in entries]
 
477
 
 
478
    def stat(self, relpath):
 
479
        """Return the stat information for a file.
 
480
        """
 
481
        path = relpath
 
482
        try:
 
483
            path = self._abspath(relpath)
 
484
            return os.lstat(path)
 
485
        except (IOError, OSError),e:
 
486
            self._translate_error(e, path)
 
487
 
 
488
    def lock_read(self, relpath):
 
489
        """Lock the given file for shared (read) access.
 
490
        :return: A lock object, which should be passed to Transport.unlock()
 
491
        """
 
492
        from bzrlib.lock import ReadLock
 
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)
 
499
 
 
500
    def lock_write(self, relpath):
 
501
        """Lock the given file for exclusive (write) access.
 
502
        WARNING: many transports do not support this, so trying avoid using it
 
503
 
 
504
        :return: A lock object, which should be passed to Transport.unlock()
 
505
        """
 
506
        from bzrlib.lock import WriteLock
 
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),]