/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: Canonical.com Patch Queue Manager
  • Date: 2008-07-30 11:38:09 UTC
  • mfrom: (3582.1.7 stacking)
  • Revision ID: pqm@pqm.ubuntu.com-20080730113809-meoemzm18iybvd4k
(mbp) add_fallback_repository gives more detail on incompatibilities

Show diffs side-by-side

added added

removed removed

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