~bzr-pqm/bzr/bzr.dev

1553.5.16 by Martin Pool
MemoryTransport.rename() must raise exceptions on collision
1
# Copyright (C) 2005, 2006 Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
1442.1.44 by Robert Collins
Many transport related tweaks:
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.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
1442.1.44 by Robert Collins
Many transport related tweaks:
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.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
1442.1.44 by Robert Collins
Many transport related tweaks:
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
1553.5.14 by Martin Pool
doc
16
17
"""Implementation of Transport that uses memory for its storage.
18
19
The contents of the transport will be lost when the object is discarded,
20
so this is primarily useful for testing.
21
"""
1442.1.44 by Robert Collins
Many transport related tweaks:
22
23
import os
24
import errno
1553.5.16 by Martin Pool
MemoryTransport.rename() must raise exceptions on collision
25
import re
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
26
from stat import S_IFREG, S_IFDIR
1442.1.44 by Robert Collins
Many transport related tweaks:
27
from cStringIO import StringIO
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
28
import warnings
1442.1.44 by Robert Collins
Many transport related tweaks:
29
2586.1.1 by Robert Collins
* New method ``external_url`` on Transport for obtaining the url to
30
from bzrlib.errors import (
31
    FileExists,
32
    LockError,
33
    InProcessTransport,
34
    NoSuchFile,
35
    TransportError,
36
    )
1442.1.44 by Robert Collins
Many transport related tweaks:
37
from bzrlib.trace import mutter
1685.1.45 by John Arbash Meinel
Moved url functions into bzrlib.urlutils
38
from bzrlib.transport import (Transport, register_transport, Server)
39
import bzrlib.urlutils as urlutils
1442.1.44 by Robert Collins
Many transport related tweaks:
40
41
1558.10.2 by Robert Collins
Refactor the FakeNFS support into a TransportDecorator.
42
1442.1.44 by Robert Collins
Many transport related tweaks:
43
class MemoryStat(object):
44
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
45
    def __init__(self, size, is_dir, perms):
1442.1.44 by Robert Collins
Many transport related tweaks:
46
        self.st_size = size
1530.1.3 by Robert Collins
transport implementations now tested consistently.
47
        if not is_dir:
1553.5.65 by Martin Pool
MemoryTransport: Set better permissions on fake directory inodes
48
            if perms is None:
49
                perms = 0644
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
50
            self.st_mode = S_IFREG | perms
1530.1.3 by Robert Collins
transport implementations now tested consistently.
51
        else:
1553.5.65 by Martin Pool
MemoryTransport: Set better permissions on fake directory inodes
52
            if perms is None:
53
                perms = 0755
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
54
            self.st_mode = S_IFDIR | perms
1442.1.44 by Robert Collins
Many transport related tweaks:
55
56
57
class MemoryTransport(Transport):
1534.4.9 by Robert Collins
Add a readonly decorator for transports.
58
    """This is an in memory file system for transient data storage."""
1442.1.44 by Robert Collins
Many transport related tweaks:
59
1530.1.3 by Robert Collins
transport implementations now tested consistently.
60
    def __init__(self, url=""):
1442.1.44 by Robert Collins
Many transport related tweaks:
61
        """Set the 'base' path where files will be stored."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
62
        if url == "":
1685.1.42 by John Arbash Meinel
A couple more fixes to make sure memory:/// works correctly.
63
            url = "memory:///"
1530.1.3 by Robert Collins
transport implementations now tested consistently.
64
        if url[-1] != '/':
65
            url = url + '/'
66
        super(MemoryTransport, self).__init__(url)
1910.16.2 by Andrew Bennetts
Reduce transport code duplication by creating a '_combine_paths' method to Transport.
67
        split = url.find(':') + 3
68
        self._scheme = url[:split]
69
        self._cwd = url[split:]
1553.5.16 by Martin Pool
MemoryTransport.rename() must raise exceptions on collision
70
        # dictionaries from absolute path to file mode
1685.1.44 by John Arbash Meinel
Now all MemoryTransports start with a valid root.
71
        self._dirs = {'/':None}
1442.1.44 by Robert Collins
Many transport related tweaks:
72
        self._files = {}
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
73
        self._locks = {}
1442.1.44 by Robert Collins
Many transport related tweaks:
74
75
    def clone(self, offset=None):
76
        """See Transport.clone()."""
1910.16.2 by Andrew Bennetts
Reduce transport code duplication by creating a '_combine_paths' method to Transport.
77
        path = self._combine_paths(self._cwd, offset)
78
        if len(path) == 0 or path[-1] != '/':
79
            path += '/'
80
        url = self._scheme + path
1530.1.3 by Robert Collins
transport implementations now tested consistently.
81
        result = MemoryTransport(url)
82
        result._dirs = self._dirs
83
        result._files = self._files
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
84
        result._locks = self._locks
1530.1.3 by Robert Collins
transport implementations now tested consistently.
85
        return result
1442.1.44 by Robert Collins
Many transport related tweaks:
86
87
    def abspath(self, relpath):
88
        """See Transport.abspath()."""
1636.1.1 by Robert Collins
Fix calling relpath() and abspath() on transports at their root.
89
        # while a little slow, this is sufficiently fast to not matter in our
90
        # current environment - XXX RBC 20060404 move the clone '..' handling
91
        # into here and call abspath from clone
92
        temp_t = self.clone(relpath)
1685.1.42 by John Arbash Meinel
A couple more fixes to make sure memory:/// works correctly.
93
        if temp_t.base.count('/') == 3:
1636.1.1 by Robert Collins
Fix calling relpath() and abspath() on transports at their root.
94
            return temp_t.base
95
        else:
96
            return temp_t.base[:-1]
1442.1.44 by Robert Collins
Many transport related tweaks:
97
1955.3.15 by John Arbash Meinel
Deprecate 'Transport.append' in favor of Transport.append_file or Transport.append_bytes
98
    def append_file(self, relpath, f, mode=None):
1955.3.16 by John Arbash Meinel
Switch over to Transport.append_bytes or append_files
99
        """See Transport.append_file()."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
100
        _abspath = self._abspath(relpath)
101
        self._check_parent(_abspath)
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
102
        orig_content, orig_mode = self._files.get(_abspath, ("", None))
1666.1.6 by Robert Collins
Make knit the default format.
103
        if mode is None:
104
            mode = orig_mode
105
        self._files[_abspath] = (orig_content + f.read(), mode)
1563.2.3 by Robert Collins
Change the return signature of transport.append and append_multi to return the length of the pre-append content.
106
        return len(orig_content)
1442.1.44 by Robert Collins
Many transport related tweaks:
107
1530.1.3 by Robert Collins
transport implementations now tested consistently.
108
    def _check_parent(self, _abspath):
109
        dir = os.path.dirname(_abspath)
110
        if dir != '/':
1442.1.44 by Robert Collins
Many transport related tweaks:
111
            if not dir in self._dirs:
1530.1.3 by Robert Collins
transport implementations now tested consistently.
112
                raise NoSuchFile(_abspath)
1442.1.44 by Robert Collins
Many transport related tweaks:
113
114
    def has(self, relpath):
115
        """See Transport.has()."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
116
        _abspath = self._abspath(relpath)
1711.9.11 by John Arbash Meinel
change return foo in bar to return (foo in bar)
117
        return (_abspath in self._files) or (_abspath in self._dirs)
1530.1.3 by Robert Collins
transport implementations now tested consistently.
118
119
    def delete(self, relpath):
120
        """See Transport.delete()."""
121
        _abspath = self._abspath(relpath)
122
        if not _abspath in self._files:
123
            raise NoSuchFile(relpath)
124
        del self._files[_abspath]
1442.1.44 by Robert Collins
Many transport related tweaks:
125
2586.1.1 by Robert Collins
* New method ``external_url`` on Transport for obtaining the url to
126
    def external_url(self):
127
        """See bzrlib.transport.Transport.external_url."""
128
        # MemoryTransport's are only accessible in-process
129
        # so we raise here
130
        raise InProcessTransport(self)
131
2164.2.15 by Vincent Ladeuil
Http redirections are not followed by default. Do not use hints
132
    def get(self, relpath):
1442.1.44 by Robert Collins
Many transport related tweaks:
133
        """See Transport.get()."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
134
        _abspath = self._abspath(relpath)
135
        if not _abspath in self._files:
1442.1.44 by Robert Collins
Many transport related tweaks:
136
            raise NoSuchFile(relpath)
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
137
        return StringIO(self._files[_abspath][0])
1442.1.44 by Robert Collins
Many transport related tweaks:
138
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
139
    def put_file(self, relpath, f, mode=None):
140
        """See Transport.put_file()."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
141
        _abspath = self._abspath(relpath)
142
        self._check_parent(_abspath)
2018.5.131 by Andrew Bennetts
Be strict about unicode passed to transport.put_{bytes,file} and SmartClient.call_with_body_bytes, fixing part of TestLockableFiles_RemoteLockDir.test_read_write.
143
        bytes = f.read()
144
        if type(bytes) is not str:
145
            # Although not strictly correct, we raise UnicodeEncodeError to be
146
            # compatible with other transports.
147
            raise UnicodeEncodeError(
148
                'undefined', bytes, 0, 1,
149
                'put_file must be given a file of bytes, not unicode.')
150
        self._files[_abspath] = (bytes, mode)
1442.1.44 by Robert Collins
Many transport related tweaks:
151
1185.58.4 by John Arbash Meinel
Added permission checking to Branch, and propogated that change into the stores.
152
    def mkdir(self, relpath, mode=None):
1442.1.44 by Robert Collins
Many transport related tweaks:
153
        """See Transport.mkdir()."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
154
        _abspath = self._abspath(relpath)
155
        self._check_parent(_abspath)
156
        if _abspath in self._dirs:
1442.1.44 by Robert Collins
Many transport related tweaks:
157
            raise FileExists(relpath)
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
158
        self._dirs[_abspath]=mode
1442.1.44 by Robert Collins
Many transport related tweaks:
159
160
    def listable(self):
161
        """See Transport.listable."""
162
        return True
163
164
    def iter_files_recursive(self):
1530.1.3 by Robert Collins
transport implementations now tested consistently.
165
        for file in self._files:
166
            if file.startswith(self._cwd):
1959.2.4 by John Arbash Meinel
MemoryTransport.iter_files_recursive() was not returning escaped paths
167
                yield urlutils.escape(file[len(self._cwd):])
1442.1.44 by Robert Collins
Many transport related tweaks:
168
    
1530.1.3 by Robert Collins
transport implementations now tested consistently.
169
    def list_dir(self, relpath):
170
        """See Transport.list_dir()."""
171
        _abspath = self._abspath(relpath)
172
        if _abspath != '/' and _abspath not in self._dirs:
173
            raise NoSuchFile(relpath)
174
        result = []
2120.3.1 by John Arbash Meinel
Fix MemoryTransport.list_dir() implementation, and update tests
175
176
        if not _abspath.endswith('/'):
177
            _abspath += '/'
178
179
        for path_group in self._files, self._dirs:
180
            for path in path_group:
181
                if path.startswith(_abspath):
182
                    trailing = path[len(_abspath):]
183
                    if trailing and '/' not in trailing:
184
                        result.append(trailing)
1910.7.1 by Andrew Bennetts
Make sure list_dir always returns url-escaped names.
185
        return map(urlutils.escape, result)
1553.5.16 by Martin Pool
MemoryTransport.rename() must raise exceptions on collision
186
187
    def rename(self, rel_from, rel_to):
188
        """Rename a file or directory; fail if the destination exists"""
189
        abs_from = self._abspath(rel_from)
190
        abs_to = self._abspath(rel_to)
191
        def replace(x):
192
            if x == abs_from:
193
                x = abs_to
194
            elif x.startswith(abs_from + '/'):
195
                x = abs_to + x[len(abs_from):]
196
            return x
197
        def do_renames(container):
198
            for path in container:
199
                new_path = replace(path)
200
                if new_path != path:
201
                    if new_path in container:
202
                        raise FileExists(new_path)
203
                    container[new_path] = container[path]
204
                    del container[path]
205
        do_renames(self._files)
206
        do_renames(self._dirs)
1442.1.44 by Robert Collins
Many transport related tweaks:
207
    
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
208
    def rmdir(self, relpath):
209
        """See Transport.rmdir."""
210
        _abspath = self._abspath(relpath)
211
        if _abspath in self._files:
212
            self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
213
        for path in self._files:
2338.5.1 by Andrew Bennetts
Fix bug in MemoryTransport.rmdir.
214
            if path.startswith(_abspath + '/'):
1553.5.15 by Martin Pool
MemoryTransport should indicate ENOTEMPTY on rmdir of nonempty, same as unix
215
                self._translate_error(IOError(errno.ENOTEMPTY, relpath),
216
                                      relpath)
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
217
        for path in self._dirs:
2338.5.1 by Andrew Bennetts
Fix bug in MemoryTransport.rmdir.
218
            if path.startswith(_abspath + '/') and path != _abspath:
1553.5.10 by Martin Pool
New DirectoryNotEmpty exception, and raise this from local and memory
219
                self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
220
        if not _abspath in self._dirs:
221
            raise NoSuchFile(relpath)
222
        del self._dirs[_abspath]
223
1442.1.44 by Robert Collins
Many transport related tweaks:
224
    def stat(self, relpath):
225
        """See Transport.stat()."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
226
        _abspath = self._abspath(relpath)
227
        if _abspath in self._files:
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
228
            return MemoryStat(len(self._files[_abspath][0]), False, 
229
                              self._files[_abspath][1])
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
230
        elif _abspath in self._dirs:
231
            return MemoryStat(0, True, self._dirs[_abspath])
1530.1.3 by Robert Collins
transport implementations now tested consistently.
232
        else:
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
233
            raise NoSuchFile(_abspath)
234
235
    def lock_read(self, relpath):
236
        """See Transport.lock_read()."""
237
        return _MemoryLock(self._abspath(relpath), self)
238
239
    def lock_write(self, relpath):
240
        """See Transport.lock_write()."""
241
        return _MemoryLock(self._abspath(relpath), self)
1530.1.3 by Robert Collins
transport implementations now tested consistently.
242
243
    def _abspath(self, relpath):
244
        """Generate an internal absolute path."""
1685.1.45 by John Arbash Meinel
Moved url functions into bzrlib.urlutils
245
        relpath = urlutils.unescape(relpath)
1530.1.3 by Robert Collins
transport implementations now tested consistently.
246
        if relpath.find('..') != -1:
247
            raise AssertionError('relpath contains ..')
1986.1.10 by Robert Collins
Merge from bzr.dev, fixing found bugs handling 'has('/')' in MemoryTransport and SFTP transports.
248
        if relpath == '':
249
            return '/'
1910.15.1 by Andrew Bennetts
More tests for abspath and clone behaviour
250
        if relpath[0] == '/':
251
            return relpath
1530.1.3 by Robert Collins
transport implementations now tested consistently.
252
        if relpath == '.':
1685.1.44 by John Arbash Meinel
Now all MemoryTransports start with a valid root.
253
            if (self._cwd == '/'):
254
                return self._cwd
1530.1.3 by Robert Collins
transport implementations now tested consistently.
255
            return self._cwd[:-1]
256
        if relpath.endswith('/'):
257
            relpath = relpath[:-1]
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
258
        if relpath.startswith('./'):
259
            relpath = relpath[2:]
1530.1.3 by Robert Collins
transport implementations now tested consistently.
260
        return self._cwd + relpath
261
262
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
263
class _MemoryLock(object):
264
    """This makes a lock."""
265
266
    def __init__(self, path, transport):
267
        assert isinstance(transport, MemoryTransport)
268
        self.path = path
269
        self.transport = transport
270
        if self.path in self.transport._locks:
271
            raise LockError('File %r already locked' % (self.path,))
272
        self.transport._locks[self.path] = self
273
274
    def __del__(self):
275
        # Should this warn, or actually try to cleanup?
276
        if self.transport:
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
277
            warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
278
            self.unlock()
279
280
    def unlock(self):
281
        del self.transport._locks[self.path]
282
        self.transport = None
283
284
1530.1.3 by Robert Collins
transport implementations now tested consistently.
285
class MemoryServer(Server):
286
    """Server for the MemoryTransport for testing with."""
287
1558.10.2 by Robert Collins
Refactor the FakeNFS support into a TransportDecorator.
288
    def setUp(self):
1530.1.3 by Robert Collins
transport implementations now tested consistently.
289
        """See bzrlib.transport.Server.setUp."""
1685.1.44 by John Arbash Meinel
Now all MemoryTransports start with a valid root.
290
        self._dirs = {'/':None}
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
291
        self._files = {}
292
        self._locks = {}
1685.1.42 by John Arbash Meinel
A couple more fixes to make sure memory:/// works correctly.
293
        self._scheme = "memory+%s:///" % id(self)
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
294
        def memory_factory(url):
1558.10.2 by Robert Collins
Refactor the FakeNFS support into a TransportDecorator.
295
            result = MemoryTransport(url)
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
296
            result._dirs = self._dirs
297
            result._files = self._files
298
            result._locks = self._locks
299
            return result
300
        register_transport(self._scheme, memory_factory)
1530.1.3 by Robert Collins
transport implementations now tested consistently.
301
302
    def tearDown(self):
303
        """See bzrlib.transport.Server.tearDown."""
304
        # unregister this server
305
306
    def get_url(self):
307
        """See bzrlib.transport.Server.get_url."""
308
        return self._scheme
1530.1.11 by Robert Collins
Push the transport permutations list into each transport module allowing for automatic testing of new modules that are registered as transports.
309
310
311
def get_test_permutations():
312
    """Return the permutations to be used in testing."""
1558.10.2 by Robert Collins
Refactor the FakeNFS support into a TransportDecorator.
313
    return [(MemoryTransport, MemoryServer),
1530.1.11 by Robert Collins
Push the transport permutations list into each transport module allowing for automatic testing of new modules that are registered as transports.
314
            ]