/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

Update non_atomic_put to have a create_parent_dir flag

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 errno
 
23
import os
 
24
import shutil
 
25
import sys
 
26
from stat import ST_MODE, S_ISDIR, ST_SIZE, S_IMODE
 
27
import tempfile
 
28
 
 
29
from bzrlib import (
 
30
    osutils,
 
31
    urlutils,
 
32
    )
 
33
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename,
 
34
                            check_legal_path, rmtree)
 
35
from bzrlib.symbol_versioning import warn
 
36
from bzrlib.trace import mutter
 
37
from bzrlib.transport import Transport, Server
 
38
 
 
39
 
 
40
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY
 
41
_non_atomic_put_flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | osutils.O_BINARY
 
42
 
 
43
 
 
44
class LocalTransport(Transport):
 
45
    """This is the transport agent for local filesystem access."""
 
46
 
 
47
    def __init__(self, base):
 
48
        """Set the base path where files will be stored."""
 
49
        if not base.startswith('file://'):
 
50
            warn("Instantiating LocalTransport with a filesystem path"
 
51
                " is deprecated as of bzr 0.8."
 
52
                " Please use bzrlib.transport.get_transport()"
 
53
                " or pass in a file:// url.",
 
54
                 DeprecationWarning,
 
55
                 stacklevel=2
 
56
                 )
 
57
            base = urlutils.local_path_to_url(base)
 
58
        if base[-1] != '/':
 
59
            base = base + '/'
 
60
        super(LocalTransport, self).__init__(base)
 
61
        self._local_base = urlutils.local_path_from_url(base)
 
62
 
 
63
    def should_cache(self):
 
64
        return False
 
65
 
 
66
    def clone(self, offset=None):
 
67
        """Return a new LocalTransport with root at self.base + offset
 
68
        Because the local filesystem does not require a connection, 
 
69
        we can just return a new object.
 
70
        """
 
71
        if offset is None:
 
72
            return LocalTransport(self.base)
 
73
        else:
 
74
            return LocalTransport(self.abspath(offset))
 
75
 
 
76
    def _abspath(self, relative_reference):
 
77
        """Return a path for use in os calls.
 
78
 
 
79
        Several assumptions are made:
 
80
         - relative_reference does not contain '..'
 
81
         - relative_reference is url escaped.
 
82
        """
 
83
        if relative_reference in ('.', ''):
 
84
            return self._local_base
 
85
        return self._local_base + urlutils.unescape(relative_reference)
 
86
 
 
87
    def abspath(self, relpath):
 
88
        """Return the full url to the given relative URL."""
 
89
        # TODO: url escape the result. RBC 20060523.
 
90
        assert isinstance(relpath, basestring), (type(relpath), relpath)
 
91
        # jam 20060426 Using normpath on the real path, because that ensures
 
92
        #       proper handling of stuff like
 
93
        path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
 
94
        return urlutils.local_path_to_url(path)
 
95
 
 
96
    def local_abspath(self, relpath):
 
97
        """Transform the given relative path URL into the actual path on disk
 
98
 
 
99
        This function only exists for the LocalTransport, since it is
 
100
        the only one that has direct local access.
 
101
        This is mostly for stuff like WorkingTree which needs to know
 
102
        the local working directory.
 
103
        
 
104
        This function is quite expensive: it calls realpath which resolves
 
105
        symlinks.
 
106
        """
 
107
        absurl = self.abspath(relpath)
 
108
        # mutter(u'relpath %s => base: %s, absurl %s', relpath, self.base, absurl)
 
109
        return urlutils.local_path_from_url(absurl)
 
110
 
 
111
    def relpath(self, abspath):
 
112
        """Return the local path portion from a given absolute path.
 
113
        """
 
114
        if abspath is None:
 
115
            abspath = u'.'
 
116
 
 
117
        return urlutils.file_relpath(
 
118
            urlutils.strip_trailing_slash(self.base), 
 
119
            urlutils.strip_trailing_slash(abspath))
 
120
 
 
121
    def has(self, relpath):
 
122
        return os.access(self._abspath(relpath), os.F_OK)
 
123
 
 
124
    def get(self, relpath):
 
125
        """Get the file at the given relative path.
 
126
 
 
127
        :param relpath: The relative path to the file
 
128
        """
 
129
        try:
 
130
            path = self._abspath(relpath)
 
131
            return open(path, 'rb')
 
132
        except (IOError, OSError),e:
 
133
            self._translate_error(e, path)
 
134
 
 
135
    def put(self, relpath, f, mode=None):
 
136
        """Copy the file-like object into the location.
 
137
 
 
138
        :param relpath: Location to put the contents, relative to base.
 
139
        :param f:       File-like object.
 
140
        :param mode: The mode for the newly created file, 
 
141
                     None means just use the default
 
142
        """
 
143
        from bzrlib.atomicfile import AtomicFile
 
144
 
 
145
        path = relpath
 
146
        try:
 
147
            path = self._abspath(relpath)
 
148
            check_legal_path(path)
 
149
            fp = AtomicFile(path, 'wb', new_mode=mode)
 
150
        except (IOError, OSError),e:
 
151
            self._translate_error(e, path)
 
152
        try:
 
153
            self._pump(f, fp)
 
154
            fp.commit()
 
155
        finally:
 
156
            fp.close()
 
157
 
 
158
    def non_atomic_put(self, relpath, f, mode=None, create_parent_dir=False):
 
159
        """Copy the file-like object into the target location.
 
160
 
 
161
        This function is not strictly safe to use. It is only meant to
 
162
        be used when you already know that the target does not exist.
 
163
        It is not safe, because it will open and truncate the remote
 
164
        file. So there may be a time when the file has invalid contents.
 
165
 
 
166
        :param relpath: The remote location to put the contents.
 
167
        :param f:       File-like object.
 
168
        :param mode:    Possible access permissions for new file.
 
169
                        None means do not set remote permissions.
 
170
        :param create_parent_dir: If we cannot create the target file because
 
171
                        the parent directory does not exist, go ahead and
 
172
                        create it, and then try again.
 
173
        """
 
174
        abspath = self._abspath(relpath)
 
175
        if mode is None:
 
176
            # os.open() will automatically use the umask
 
177
            local_mode = 0666
 
178
        else:
 
179
            local_mode = mode
 
180
        try:
 
181
            fd = os.open(abspath, _non_atomic_put_flags, local_mode)
 
182
        except (IOError, OSError),e:
 
183
            # We couldn't create the file, maybe we need to create
 
184
            # the parent directory, and try again
 
185
            if (not create_parent_dir
 
186
                or e.errno not in (errno.ENOENT,errno.ENOTDIR)):
 
187
                self._translate_error(e, relpath)
 
188
            parent_dir = os.path.dirname(abspath)
 
189
            if not parent_dir:
 
190
                self._translate_error(e, relpath)
 
191
            try:
 
192
                os.mkdir(parent_dir)
 
193
            except (IOError, OSError), e:
 
194
                self._translate_error(e, relpath)
 
195
            # We created the parent directory, lets try to open the
 
196
            # file again
 
197
            try:
 
198
                fd = os.open(abspath, _non_atomic_put_flags, local_mode)
 
199
            except (IOError, OSError), e:
 
200
                self._translate_error(e, relpath)
 
201
        try:
 
202
            st = os.fstat(fd)
 
203
            if mode is not None and mode != S_IMODE(st.st_mode):
 
204
                # Because of umask, we may still need to chmod the file.
 
205
                # But in the general case, we won't have to
 
206
                os.chmod(abspath, mode)
 
207
            self._pump_to_fd(f, fd)
 
208
        finally:
 
209
            os.close(fd)
 
210
 
 
211
    def iter_files_recursive(self):
 
212
        """Iter the relative paths of files in the transports sub-tree."""
 
213
        queue = list(self.list_dir(u'.'))
 
214
        while queue:
 
215
            relpath = queue.pop(0)
 
216
            st = self.stat(relpath)
 
217
            if S_ISDIR(st[ST_MODE]):
 
218
                for i, basename in enumerate(self.list_dir(relpath)):
 
219
                    queue.insert(i, relpath+'/'+basename)
 
220
            else:
 
221
                yield relpath
 
222
 
 
223
    def mkdir(self, relpath, mode=None):
 
224
        """Create a directory at the given path."""
 
225
        path = relpath
 
226
        try:
 
227
            if mode is None:
 
228
                # os.mkdir() will filter through umask
 
229
                local_mode = 0777
 
230
            else:
 
231
                local_mode = mode
 
232
            path = self._abspath(relpath)
 
233
            os.mkdir(path, local_mode)
 
234
            if mode is not None:
 
235
                # It is probably faster to just do the chmod, rather than
 
236
                # doing a stat, and then trying to compare
 
237
                os.chmod(path, mode)
 
238
        except (IOError, OSError),e:
 
239
            self._translate_error(e, path)
 
240
 
 
241
    def append(self, relpath, f, mode=None):
 
242
        """Append the text in the file-like object into the final location."""
 
243
        abspath = self._abspath(relpath)
 
244
        if mode is None:
 
245
            # os.open() will automatically use the umask
 
246
            local_mode = 0666
 
247
        else:
 
248
            local_mode = mode
 
249
        try:
 
250
            fd = os.open(abspath, _append_flags, local_mode)
 
251
        except (IOError, OSError),e:
 
252
            self._translate_error(e, relpath)
 
253
        try:
 
254
            st = os.fstat(fd)
 
255
            result = st.st_size
 
256
            if mode is not None and mode != S_IMODE(st.st_mode):
 
257
                # Because of umask, we may still need to chmod the file.
 
258
                # But in the general case, we won't have to
 
259
                os.chmod(abspath, mode)
 
260
            self._pump_to_fd(f, fd)
 
261
        finally:
 
262
            os.close(fd)
 
263
        return result
 
264
 
 
265
    def _pump_to_fd(self, fromfile, to_fd):
 
266
        """Copy contents of one file to another."""
 
267
        BUFSIZE = 32768
 
268
        while True:
 
269
            b = fromfile.read(BUFSIZE)
 
270
            if not b:
 
271
                break
 
272
            os.write(to_fd, b)
 
273
 
 
274
    def copy(self, rel_from, rel_to):
 
275
        """Copy the item at rel_from to the location at rel_to"""
 
276
        path_from = self._abspath(rel_from)
 
277
        path_to = self._abspath(rel_to)
 
278
        try:
 
279
            shutil.copy(path_from, path_to)
 
280
        except (IOError, OSError),e:
 
281
            # TODO: What about path_to?
 
282
            self._translate_error(e, path_from)
 
283
 
 
284
    def rename(self, rel_from, rel_to):
 
285
        path_from = self._abspath(rel_from)
 
286
        try:
 
287
            # *don't* call bzrlib.osutils.rename, because we want to 
 
288
            # detect errors on rename
 
289
            os.rename(path_from, self._abspath(rel_to))
 
290
        except (IOError, OSError),e:
 
291
            # TODO: What about path_to?
 
292
            self._translate_error(e, path_from)
 
293
 
 
294
    def move(self, rel_from, rel_to):
 
295
        """Move the item at rel_from to the location at rel_to"""
 
296
        path_from = self._abspath(rel_from)
 
297
        path_to = self._abspath(rel_to)
 
298
 
 
299
        try:
 
300
            # this version will delete the destination if necessary
 
301
            rename(path_from, path_to)
 
302
        except (IOError, OSError),e:
 
303
            # TODO: What about path_to?
 
304
            self._translate_error(e, path_from)
 
305
 
 
306
    def delete(self, relpath):
 
307
        """Delete the item at relpath"""
 
308
        path = relpath
 
309
        try:
 
310
            path = self._abspath(relpath)
 
311
            os.remove(path)
 
312
        except (IOError, OSError),e:
 
313
            self._translate_error(e, path)
 
314
 
 
315
    def copy_to(self, relpaths, other, mode=None, pb=None):
 
316
        """Copy a set of entries from self into another Transport.
 
317
 
 
318
        :param relpaths: A list/generator of entries to be copied.
 
319
        """
 
320
        if isinstance(other, LocalTransport):
 
321
            # Both from & to are on the local filesystem
 
322
            # Unfortunately, I can't think of anything faster than just
 
323
            # copying them across, one by one :(
 
324
            total = self._get_total(relpaths)
 
325
            count = 0
 
326
            for path in relpaths:
 
327
                self._update_pb(pb, 'copy-to', count, total)
 
328
                try:
 
329
                    mypath = self._abspath(path)
 
330
                    otherpath = other._abspath(path)
 
331
                    shutil.copy(mypath, otherpath)
 
332
                    if mode is not None:
 
333
                        os.chmod(otherpath, mode)
 
334
                except (IOError, OSError),e:
 
335
                    self._translate_error(e, path)
 
336
                count += 1
 
337
            return count
 
338
        else:
 
339
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
 
340
 
 
341
    def listable(self):
 
342
        """See Transport.listable."""
 
343
        return True
 
344
 
 
345
    def list_dir(self, relpath):
 
346
        """Return a list of all files at the given location.
 
347
        WARNING: many transports do not support this, so trying avoid using
 
348
        it if at all possible.
 
349
        """
 
350
        path = self._abspath(relpath)
 
351
        try:
 
352
            return [urlutils.escape(entry) for entry in os.listdir(path)]
 
353
        except (IOError, OSError), e:
 
354
            self._translate_error(e, path)
 
355
 
 
356
    def stat(self, relpath):
 
357
        """Return the stat information for a file.
 
358
        """
 
359
        path = relpath
 
360
        try:
 
361
            path = self._abspath(relpath)
 
362
            return os.stat(path)
 
363
        except (IOError, OSError),e:
 
364
            self._translate_error(e, path)
 
365
 
 
366
    def lock_read(self, relpath):
 
367
        """Lock the given file for shared (read) access.
 
368
        :return: A lock object, which should be passed to Transport.unlock()
 
369
        """
 
370
        from bzrlib.lock import ReadLock
 
371
        path = relpath
 
372
        try:
 
373
            path = self._abspath(relpath)
 
374
            return ReadLock(path)
 
375
        except (IOError, OSError), e:
 
376
            self._translate_error(e, path)
 
377
 
 
378
    def lock_write(self, relpath):
 
379
        """Lock the given file for exclusive (write) access.
 
380
        WARNING: many transports do not support this, so trying avoid using it
 
381
 
 
382
        :return: A lock object, which should be passed to Transport.unlock()
 
383
        """
 
384
        from bzrlib.lock import WriteLock
 
385
        return WriteLock(self._abspath(relpath))
 
386
 
 
387
    def rmdir(self, relpath):
 
388
        """See Transport.rmdir."""
 
389
        path = relpath
 
390
        try:
 
391
            path = self._abspath(relpath)
 
392
            os.rmdir(path)
 
393
        except (IOError, OSError),e:
 
394
            self._translate_error(e, path)
 
395
 
 
396
    def _can_roundtrip_unix_modebits(self):
 
397
        if sys.platform == 'win32':
 
398
            # anyone else?
 
399
            return False
 
400
        else:
 
401
            return True
 
402
 
 
403
 
 
404
class LocalRelpathServer(Server):
 
405
    """A pretend server for local transports, using relpaths."""
 
406
 
 
407
    def get_url(self):
 
408
        """See Transport.Server.get_url."""
 
409
        return "."
 
410
 
 
411
 
 
412
class LocalAbspathServer(Server):
 
413
    """A pretend server for local transports, using absolute paths."""
 
414
 
 
415
    def get_url(self):
 
416
        """See Transport.Server.get_url."""
 
417
        return os.path.abspath("")
 
418
 
 
419
 
 
420
class LocalURLServer(Server):
 
421
    """A pretend server for local transports, using file:// urls."""
 
422
 
 
423
    def get_url(self):
 
424
        """See Transport.Server.get_url."""
 
425
        return urlutils.local_path_to_url('')
 
426
 
 
427
 
 
428
def get_test_permutations():
 
429
    """Return the permutations to be used in testing."""
 
430
    return [(LocalTransport, LocalRelpathServer),
 
431
            (LocalTransport, LocalAbspathServer),
 
432
            (LocalTransport, LocalURLServer),
 
433
            ]