/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: Robert Collins
  • Date: 2006-09-04 08:00:48 UTC
  • mfrom: (1979.2.1 merge_from_branch)
  • mto: This revision was merged to the branch mainline in revision 1982.
  • Revision ID: robertc@robertcollins.net-20060904080048-aff24d32f888b21d
(robertc) integrate merge_from_branch patch: adds a convenience method "merge_from_branch" to WorkingTree.

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
            entries = os.listdir(path)
 
296
        except (IOError, OSError), e:
 
297
            self._translate_error(e, path)
 
298
        return [urlutils.escape(entry) for entry in entries]
 
299
 
 
300
    def stat(self, relpath):
 
301
        """Return the stat information for a file.
 
302
        """
 
303
        path = relpath
 
304
        try:
 
305
            path = self._abspath(relpath)
 
306
            return os.stat(path)
 
307
        except (IOError, OSError),e:
 
308
            self._translate_error(e, path)
 
309
 
 
310
    def lock_read(self, relpath):
 
311
        """Lock the given file for shared (read) access.
 
312
        :return: A lock object, which should be passed to Transport.unlock()
 
313
        """
 
314
        from bzrlib.lock import ReadLock
 
315
        path = relpath
 
316
        try:
 
317
            path = self._abspath(relpath)
 
318
            return ReadLock(path)
 
319
        except (IOError, OSError), e:
 
320
            self._translate_error(e, path)
 
321
 
 
322
    def lock_write(self, relpath):
 
323
        """Lock the given file for exclusive (write) access.
 
324
        WARNING: many transports do not support this, so trying avoid using it
 
325
 
 
326
        :return: A lock object, which should be passed to Transport.unlock()
 
327
        """
 
328
        from bzrlib.lock import WriteLock
 
329
        return WriteLock(self._abspath(relpath))
 
330
 
 
331
    def rmdir(self, relpath):
 
332
        """See Transport.rmdir."""
 
333
        path = relpath
 
334
        try:
 
335
            path = self._abspath(relpath)
 
336
            os.rmdir(path)
 
337
        except (IOError, OSError),e:
 
338
            self._translate_error(e, path)
 
339
 
 
340
    def _can_roundtrip_unix_modebits(self):
 
341
        if sys.platform == 'win32':
 
342
            # anyone else?
 
343
            return False
 
344
        else:
 
345
            return True
 
346
 
 
347
 
 
348
class LocalRelpathServer(Server):
 
349
    """A pretend server for local transports, using relpaths."""
 
350
 
 
351
    def get_url(self):
 
352
        """See Transport.Server.get_url."""
 
353
        return "."
 
354
 
 
355
 
 
356
class LocalAbspathServer(Server):
 
357
    """A pretend server for local transports, using absolute paths."""
 
358
 
 
359
    def get_url(self):
 
360
        """See Transport.Server.get_url."""
 
361
        return os.path.abspath("")
 
362
 
 
363
 
 
364
class LocalURLServer(Server):
 
365
    """A pretend server for local transports, using file:// urls."""
 
366
 
 
367
    def get_url(self):
 
368
        """See Transport.Server.get_url."""
 
369
        return urlutils.local_path_to_url('')
 
370
 
 
371
 
 
372
def get_test_permutations():
 
373
    """Return the permutations to be used in testing."""
 
374
    return [(LocalTransport, LocalRelpathServer),
 
375
            (LocalTransport, LocalAbspathServer),
 
376
            (LocalTransport, LocalURLServer),
 
377
            ]