~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

[merge] mpe: Add 'co' and 'ann'

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
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):
 
37
    """This is the transport agent for local filesystem access."""
 
38
 
 
39
    def __init__(self, base):
 
40
        """Set the base path where files will be stored."""
 
41
        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)
 
50
        if base[-1] != '/':
 
51
            base = base + '/'
 
52
        super(LocalTransport, self).__init__(base)
 
53
        self._local_base = urlutils.local_path_from_url(base)
 
54
        ## mutter("_local_base: %r => %r", base, self._local_base)
 
55
 
 
56
    def should_cache(self):
 
57
        return False
 
58
 
 
59
    def clone(self, offset=None):
 
60
        """Return a new LocalTransport with root at self.base + offset
 
61
        Because the local filesystem does not require a connection, 
 
62
        we can just return a new object.
 
63
        """
 
64
        if offset is None:
 
65
            return LocalTransport(self.base)
 
66
        else:
 
67
            return LocalTransport(self.abspath(offset))
 
68
 
 
69
    def _abspath(self, relative_reference):
 
70
        """Return a path for use in os calls.
 
71
 
 
72
        Several assumptions are made:
 
73
         - relative_reference does not contain '..'
 
74
         - relative_reference is url escaped.
 
75
        """
 
76
        if relative_reference in ('.', ''):
 
77
            return self._local_base
 
78
        return self._local_base + urlutils.unescape(relative_reference)
 
79
 
 
80
    def abspath(self, relpath):
 
81
        """Return the full url to the given relative URL."""
 
82
        # TODO: url escape the result. RBC 20060523.
 
83
        assert isinstance(relpath, basestring), (type(relpath), relpath)
 
84
        # jam 20060426 Using normpath on the real path, because that ensures
 
85
        #       proper handling of stuff like
 
86
        path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
 
87
        return urlutils.local_path_to_url(path)
 
88
 
 
89
    def local_abspath(self, relpath):
 
90
        """Transform the given relative path URL into the actual path on disk
 
91
 
 
92
        This function only exists for the LocalTransport, since it is
 
93
        the only one that has direct local access.
 
94
        This is mostly for stuff like WorkingTree which needs to know
 
95
        the local working directory.
 
96
        
 
97
        This function is quite expensive: it calls realpath which resolves
 
98
        symlinks.
 
99
        """
 
100
        absurl = self.abspath(relpath)
 
101
        # mutter(u'relpath %s => base: %s, absurl %s', relpath, self.base, absurl)
 
102
        return urlutils.local_path_from_url(absurl)
 
103
 
 
104
    def relpath(self, abspath):
 
105
        """Return the local path portion from a given absolute path.
 
106
        """
 
107
        if abspath is None:
 
108
            abspath = u'.'
 
109
 
 
110
        return urlutils.file_relpath(
 
111
            urlutils.strip_trailing_slash(self.base), 
 
112
            urlutils.strip_trailing_slash(abspath))
 
113
 
 
114
    def has(self, relpath):
 
115
        return os.access(self._abspath(relpath), os.F_OK)
 
116
 
 
117
    def get(self, relpath):
 
118
        """Get the file at the given relative path.
 
119
 
 
120
        :param relpath: The relative path to the file
 
121
        """
 
122
        try:
 
123
            path = self._abspath(relpath)
 
124
            return open(path, 'rb')
 
125
        except (IOError, OSError),e:
 
126
            self._translate_error(e, path)
 
127
 
 
128
    def put(self, relpath, f, mode=None):
 
129
        """Copy the file-like or string object into the location.
 
130
 
 
131
        :param relpath: Location to put the contents, relative to base.
 
132
        :param f:       File-like or string object.
 
133
        """
 
134
        from bzrlib.atomicfile import AtomicFile
 
135
 
 
136
        path = relpath
 
137
        try:
 
138
            path = self._abspath(relpath)
 
139
            check_legal_path(path)
 
140
            fp = AtomicFile(path, 'wb', new_mode=mode)
 
141
        except (IOError, OSError),e:
 
142
            self._translate_error(e, path)
 
143
        try:
 
144
            self._pump(f, fp)
 
145
            fp.commit()
 
146
        finally:
 
147
            fp.close()
 
148
 
 
149
    def iter_files_recursive(self):
 
150
        """Iter the relative paths of files in the transports sub-tree."""
 
151
        queue = list(self.list_dir(u'.'))
 
152
        while queue:
 
153
            relpath = queue.pop(0)
 
154
            st = self.stat(relpath)
 
155
            if S_ISDIR(st[ST_MODE]):
 
156
                for i, basename in enumerate(self.list_dir(relpath)):
 
157
                    queue.insert(i, relpath+'/'+basename)
 
158
            else:
 
159
                yield relpath
 
160
 
 
161
    def mkdir(self, relpath, mode=None):
 
162
        """Create a directory at the given path."""
 
163
        path = relpath
 
164
        try:
 
165
            path = self._abspath(relpath)
 
166
            os.mkdir(path)
 
167
            if mode is not None:
 
168
                os.chmod(path, mode)
 
169
        except (IOError, OSError),e:
 
170
            self._translate_error(e, path)
 
171
 
 
172
    def append(self, relpath, f, mode=None):
 
173
        """Append the text in the file-like object into the final location."""
 
174
        abspath = self._abspath(relpath)
 
175
        try:
 
176
            fp = open(abspath, 'ab')
 
177
            # FIXME should we really be chmodding every time ? RBC 20060523
 
178
            if mode is not None:
 
179
                os.chmod(abspath, mode)
 
180
        except (IOError, OSError),e:
 
181
            self._translate_error(e, relpath)
 
182
        # win32 workaround (tell on an unwritten file returns 0)
 
183
        fp.seek(0, 2)
 
184
        result = fp.tell()
 
185
        self._pump(f, fp)
 
186
        return result
 
187
 
 
188
    def copy(self, rel_from, rel_to):
 
189
        """Copy the item at rel_from to the location at rel_to"""
 
190
        path_from = self._abspath(rel_from)
 
191
        path_to = self._abspath(rel_to)
 
192
        try:
 
193
            shutil.copy(path_from, path_to)
 
194
        except (IOError, OSError),e:
 
195
            # TODO: What about path_to?
 
196
            self._translate_error(e, path_from)
 
197
 
 
198
    def rename(self, rel_from, rel_to):
 
199
        path_from = self._abspath(rel_from)
 
200
        try:
 
201
            # *don't* call bzrlib.osutils.rename, because we want to 
 
202
            # detect errors on rename
 
203
            os.rename(path_from, self._abspath(rel_to))
 
204
        except (IOError, OSError),e:
 
205
            # TODO: What about path_to?
 
206
            self._translate_error(e, path_from)
 
207
 
 
208
    def move(self, rel_from, rel_to):
 
209
        """Move the item at rel_from to the location at rel_to"""
 
210
        path_from = self._abspath(rel_from)
 
211
        path_to = self._abspath(rel_to)
 
212
 
 
213
        try:
 
214
            # this version will delete the destination if necessary
 
215
            rename(path_from, path_to)
 
216
        except (IOError, OSError),e:
 
217
            # TODO: What about path_to?
 
218
            self._translate_error(e, path_from)
 
219
 
 
220
    def delete(self, relpath):
 
221
        """Delete the item at relpath"""
 
222
        path = relpath
 
223
        try:
 
224
            path = self._abspath(relpath)
 
225
            os.remove(path)
 
226
        except (IOError, OSError),e:
 
227
            self._translate_error(e, path)
 
228
 
 
229
    def copy_to(self, relpaths, other, mode=None, pb=None):
 
230
        """Copy a set of entries from self into another Transport.
 
231
 
 
232
        :param relpaths: A list/generator of entries to be copied.
 
233
        """
 
234
        if isinstance(other, LocalTransport):
 
235
            # Both from & to are on the local filesystem
 
236
            # Unfortunately, I can't think of anything faster than just
 
237
            # copying them across, one by one :(
 
238
            total = self._get_total(relpaths)
 
239
            count = 0
 
240
            for path in relpaths:
 
241
                self._update_pb(pb, 'copy-to', count, total)
 
242
                try:
 
243
                    mypath = self._abspath(path)
 
244
                    otherpath = other._abspath(path)
 
245
                    shutil.copy(mypath, otherpath)
 
246
                    if mode is not None:
 
247
                        os.chmod(otherpath, mode)
 
248
                except (IOError, OSError),e:
 
249
                    self._translate_error(e, path)
 
250
                count += 1
 
251
            return count
 
252
        else:
 
253
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
 
254
 
 
255
    def listable(self):
 
256
        """See Transport.listable."""
 
257
        return True
 
258
 
 
259
    def list_dir(self, relpath):
 
260
        """Return a list of all files at the given location.
 
261
        WARNING: many transports do not support this, so trying avoid using
 
262
        it if at all possible.
 
263
        """
 
264
        path = self._abspath(relpath)
 
265
        try:
 
266
            return [urlutils.escape(entry) for entry in os.listdir(path)]
 
267
        except (IOError, OSError), e:
 
268
            self._translate_error(e, path)
 
269
 
 
270
    def stat(self, relpath):
 
271
        """Return the stat information for a file.
 
272
        """
 
273
        path = relpath
 
274
        try:
 
275
            path = self._abspath(relpath)
 
276
            return os.stat(path)
 
277
        except (IOError, OSError),e:
 
278
            self._translate_error(e, path)
 
279
 
 
280
    def lock_read(self, relpath):
 
281
        """Lock the given file for shared (read) access.
 
282
        :return: A lock object, which should be passed to Transport.unlock()
 
283
        """
 
284
        from bzrlib.lock import ReadLock
 
285
        path = relpath
 
286
        try:
 
287
            path = self._abspath(relpath)
 
288
            return ReadLock(path)
 
289
        except (IOError, OSError), e:
 
290
            self._translate_error(e, path)
 
291
 
 
292
    def lock_write(self, relpath):
 
293
        """Lock the given file for exclusive (write) access.
 
294
        WARNING: many transports do not support this, so trying avoid using it
 
295
 
 
296
        :return: A lock object, which should be passed to Transport.unlock()
 
297
        """
 
298
        from bzrlib.lock import WriteLock
 
299
        return WriteLock(self._abspath(relpath))
 
300
 
 
301
    def rmdir(self, relpath):
 
302
        """See Transport.rmdir."""
 
303
        path = relpath
 
304
        try:
 
305
            path = self._abspath(relpath)
 
306
            os.rmdir(path)
 
307
        except (IOError, OSError),e:
 
308
            self._translate_error(e, path)
 
309
 
 
310
    def _can_roundtrip_unix_modebits(self):
 
311
        if sys.platform == 'win32':
 
312
            # anyone else?
 
313
            return False
 
314
        else:
 
315
            return True
 
316
 
 
317
 
 
318
class LocalRelpathServer(Server):
 
319
    """A pretend server for local transports, using relpaths."""
 
320
 
 
321
    def get_url(self):
 
322
        """See Transport.Server.get_url."""
 
323
        return "."
 
324
 
 
325
 
 
326
class LocalAbspathServer(Server):
 
327
    """A pretend server for local transports, using absolute paths."""
 
328
 
 
329
    def get_url(self):
 
330
        """See Transport.Server.get_url."""
 
331
        return os.path.abspath("")
 
332
 
 
333
 
 
334
class LocalURLServer(Server):
 
335
    """A pretend server for local transports, using file:// urls."""
 
336
 
 
337
    def get_url(self):
 
338
        """See Transport.Server.get_url."""
 
339
        return urlutils.local_path_to_url('')
 
340
 
 
341
 
 
342
def get_test_permutations():
 
343
    """Return the permutations to be used in testing."""
 
344
    return [(LocalTransport, LocalRelpathServer),
 
345
            (LocalTransport, LocalAbspathServer),
 
346
            (LocalTransport, LocalURLServer),
 
347
            ]