~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: John Arbash Meinel
  • Date: 2005-06-28 22:19:00 UTC
  • mto: (0.5.85) (1185.82.1 bzr-w-changeset)
  • mto: This revision was merged to the branch mainline in revision 1738.
  • Revision ID: john@arbash-meinel.com-20050628221900-2ecec40914076c21
Some cleanup to the send_changeset work.

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