~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Martin Pool
  • Date: 2005-05-03 07:48:54 UTC
  • Revision ID: mbp@sourcefrog.net-20050503074854-adb6f9d6382e27a9
- sketchy experiments in bash and zsh completion

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