~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
1442.1.44 by Robert Collins
Many transport related tweaks:
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
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
1530.1.3 by Robert Collins
transport implementations now tested consistently.
23
from copy import copy
1442.1.44 by Robert Collins
Many transport related tweaks:
24
import os
25
import errno
1553.5.16 by Martin Pool
MemoryTransport.rename() must raise exceptions on collision
26
import re
1530.1.3 by Robert Collins
transport implementations now tested consistently.
27
from stat import *
1442.1.44 by Robert Collins
Many transport related tweaks:
28
from cStringIO import StringIO
29
30
from bzrlib.trace import mutter
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
31
from bzrlib.errors import TransportError, NoSuchFile, FileExists, LockError
1530.1.3 by Robert Collins
transport implementations now tested consistently.
32
from bzrlib.transport import Transport, register_transport, Server
1442.1.44 by Robert Collins
Many transport related tweaks:
33
34
class MemoryStat(object):
35
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
36
    def __init__(self, size, is_dir, perms):
1442.1.44 by Robert Collins
Many transport related tweaks:
37
        self.st_size = size
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
38
        if perms is None:
39
            perms = 0644
1530.1.3 by Robert Collins
transport implementations now tested consistently.
40
        if not is_dir:
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
41
            self.st_mode = S_IFREG | perms
1530.1.3 by Robert Collins
transport implementations now tested consistently.
42
        else:
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
43
            self.st_mode = S_IFDIR | perms
1442.1.44 by Robert Collins
Many transport related tweaks:
44
45
46
class MemoryTransport(Transport):
1534.4.9 by Robert Collins
Add a readonly decorator for transports.
47
    """This is an in memory file system for transient data storage."""
1442.1.44 by Robert Collins
Many transport related tweaks:
48
1530.1.3 by Robert Collins
transport implementations now tested consistently.
49
    def __init__(self, url=""):
1442.1.44 by Robert Collins
Many transport related tweaks:
50
        """Set the 'base' path where files will be stored."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
51
        if url == "":
52
            url = "memory:/"
53
        if url[-1] != '/':
54
            url = url + '/'
55
        super(MemoryTransport, self).__init__(url)
56
        self._cwd = url[url.find(':') + 1:]
1553.5.16 by Martin Pool
MemoryTransport.rename() must raise exceptions on collision
57
        # dictionaries from absolute path to file mode
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
58
        self._dirs = {}
1442.1.44 by Robert Collins
Many transport related tweaks:
59
        self._files = {}
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
60
        self._locks = {}
1442.1.44 by Robert Collins
Many transport related tweaks:
61
62
    def clone(self, offset=None):
63
        """See Transport.clone()."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
64
        if offset is None:
65
            return copy(self)
66
        segments = offset.split('/')
67
        cwdsegments = self._cwd.split('/')[:-1]
68
        while len(segments):
69
            segment = segments.pop(0)
70
            if segment == '.':
71
                continue
72
            if segment == '..':
73
                if len(cwdsegments) > 1:
74
                    cwdsegments.pop()
75
                continue
76
            cwdsegments.append(segment)
77
        url = self.base[:self.base.find(':') + 1] + '/'.join(cwdsegments) + '/'
78
        result = MemoryTransport(url)
79
        result._dirs = self._dirs
80
        result._files = self._files
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
81
        result._locks = self._locks
1530.1.3 by Robert Collins
transport implementations now tested consistently.
82
        return result
1442.1.44 by Robert Collins
Many transport related tweaks:
83
84
    def abspath(self, relpath):
85
        """See Transport.abspath()."""
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
86
        return self.base[:-1] + self._abspath(relpath)[len(self._cwd) - 1:]
1442.1.44 by Robert Collins
Many transport related tweaks:
87
88
    def append(self, relpath, f):
89
        """See Transport.append()."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
90
        _abspath = self._abspath(relpath)
91
        self._check_parent(_abspath)
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
92
        orig_content, orig_mode = self._files.get(_abspath, ("", None))
93
        self._files[_abspath] = (orig_content + f.read(), orig_mode)
1442.1.44 by Robert Collins
Many transport related tweaks:
94
1530.1.3 by Robert Collins
transport implementations now tested consistently.
95
    def _check_parent(self, _abspath):
96
        dir = os.path.dirname(_abspath)
97
        if dir != '/':
1442.1.44 by Robert Collins
Many transport related tweaks:
98
            if not dir in self._dirs:
1530.1.3 by Robert Collins
transport implementations now tested consistently.
99
                raise NoSuchFile(_abspath)
1442.1.44 by Robert Collins
Many transport related tweaks:
100
101
    def has(self, relpath):
102
        """See Transport.has()."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
103
        _abspath = self._abspath(relpath)
104
        return _abspath in self._files or _abspath in self._dirs
105
106
    def delete(self, relpath):
107
        """See Transport.delete()."""
108
        _abspath = self._abspath(relpath)
109
        if not _abspath in self._files:
110
            raise NoSuchFile(relpath)
111
        del self._files[_abspath]
1442.1.44 by Robert Collins
Many transport related tweaks:
112
113
    def get(self, relpath):
114
        """See Transport.get()."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
115
        _abspath = self._abspath(relpath)
116
        if not _abspath in self._files:
1442.1.44 by Robert Collins
Many transport related tweaks:
117
            raise NoSuchFile(relpath)
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
118
        return StringIO(self._files[_abspath][0])
1442.1.44 by Robert Collins
Many transport related tweaks:
119
1185.58.4 by John Arbash Meinel
Added permission checking to Branch, and propogated that change into the stores.
120
    def put(self, relpath, f, mode=None):
1442.1.44 by Robert Collins
Many transport related tweaks:
121
        """See Transport.put()."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
122
        _abspath = self._abspath(relpath)
123
        self._check_parent(_abspath)
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
124
        self._files[_abspath] = (f.read(), mode)
1442.1.44 by Robert Collins
Many transport related tweaks:
125
1185.58.4 by John Arbash Meinel
Added permission checking to Branch, and propogated that change into the stores.
126
    def mkdir(self, relpath, mode=None):
1442.1.44 by Robert Collins
Many transport related tweaks:
127
        """See Transport.mkdir()."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
128
        _abspath = self._abspath(relpath)
129
        self._check_parent(_abspath)
130
        if _abspath in self._dirs:
1442.1.44 by Robert Collins
Many transport related tweaks:
131
            raise FileExists(relpath)
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
132
        self._dirs[_abspath]=mode
1442.1.44 by Robert Collins
Many transport related tweaks:
133
134
    def listable(self):
135
        """See Transport.listable."""
136
        return True
137
138
    def iter_files_recursive(self):
1530.1.3 by Robert Collins
transport implementations now tested consistently.
139
        for file in self._files:
140
            if file.startswith(self._cwd):
141
                yield file[len(self._cwd):]
1442.1.44 by Robert Collins
Many transport related tweaks:
142
    
1530.1.3 by Robert Collins
transport implementations now tested consistently.
143
    def list_dir(self, relpath):
144
        """See Transport.list_dir()."""
145
        _abspath = self._abspath(relpath)
146
        if _abspath != '/' and _abspath not in self._dirs:
147
            raise NoSuchFile(relpath)
148
        result = []
149
        for path in self._files:
150
            if (path.startswith(_abspath) and 
151
                path[len(_abspath) + 1:].find('/') == -1 and
152
                len(path) > len(_abspath)):
153
                result.append(path[len(_abspath) + 1:])
154
        for path in self._dirs:
155
            if (path.startswith(_abspath) and 
156
                path[len(_abspath) + 1:].find('/') == -1 and
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
157
                len(path) > len(_abspath) and
158
                path[len(_abspath)] == '/'):
1530.1.3 by Robert Collins
transport implementations now tested consistently.
159
                result.append(path[len(_abspath) + 1:])
160
        return result
1553.5.16 by Martin Pool
MemoryTransport.rename() must raise exceptions on collision
161
162
    def rename(self, rel_from, rel_to):
163
        """Rename a file or directory; fail if the destination exists"""
164
        abs_from = self._abspath(rel_from)
165
        abs_to = self._abspath(rel_to)
166
        def replace(x):
167
            if x == abs_from:
168
                x = abs_to
169
            elif x.startswith(abs_from + '/'):
170
                x = abs_to + x[len(abs_from):]
171
            return x
172
        def do_renames(container):
173
            for path in container:
174
                new_path = replace(path)
175
                if new_path != path:
176
                    if new_path in container:
177
                        raise FileExists(new_path)
178
                    container[new_path] = container[path]
179
                    del container[path]
180
        do_renames(self._files)
181
        do_renames(self._dirs)
1442.1.44 by Robert Collins
Many transport related tweaks:
182
    
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
183
    def rmdir(self, relpath):
184
        """See Transport.rmdir."""
185
        _abspath = self._abspath(relpath)
186
        if _abspath in self._files:
187
            self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
188
        for path in self._files:
189
            if path.startswith(_abspath):
1553.5.15 by Martin Pool
MemoryTransport should indicate ENOTEMPTY on rmdir of nonempty, same as unix
190
                self._translate_error(IOError(errno.ENOTEMPTY, relpath),
191
                                      relpath)
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
192
        for path in self._dirs:
193
            if path.startswith(_abspath) and path != _abspath:
1553.5.10 by Martin Pool
New DirectoryNotEmpty exception, and raise this from local and memory
194
                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.
195
        if not _abspath in self._dirs:
196
            raise NoSuchFile(relpath)
197
        del self._dirs[_abspath]
198
1442.1.44 by Robert Collins
Many transport related tweaks:
199
    def stat(self, relpath):
200
        """See Transport.stat()."""
1530.1.3 by Robert Collins
transport implementations now tested consistently.
201
        _abspath = self._abspath(relpath)
202
        if _abspath in self._files:
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
203
            return MemoryStat(len(self._files[_abspath][0]), False, 
204
                              self._files[_abspath][1])
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
205
        elif _abspath == '':
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
206
            return MemoryStat(0, True, None)
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
207
        elif _abspath in self._dirs:
208
            return MemoryStat(0, True, self._dirs[_abspath])
1530.1.3 by Robert Collins
transport implementations now tested consistently.
209
        else:
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
210
            raise NoSuchFile(_abspath)
211
212
    def lock_read(self, relpath):
213
        """See Transport.lock_read()."""
214
        return _MemoryLock(self._abspath(relpath), self)
215
216
    def lock_write(self, relpath):
217
        """See Transport.lock_write()."""
218
        return _MemoryLock(self._abspath(relpath), self)
1530.1.3 by Robert Collins
transport implementations now tested consistently.
219
220
    def _abspath(self, relpath):
221
        """Generate an internal absolute path."""
222
        if relpath.find('..') != -1:
223
            raise AssertionError('relpath contains ..')
224
        if relpath == '.':
225
            return self._cwd[:-1]
226
        if relpath.endswith('/'):
227
            relpath = relpath[:-1]
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
228
        if relpath.startswith('./'):
229
            relpath = relpath[2:]
1530.1.3 by Robert Collins
transport implementations now tested consistently.
230
        return self._cwd + relpath
231
232
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
233
class _MemoryLock(object):
234
    """This makes a lock."""
235
236
    def __init__(self, path, transport):
237
        assert isinstance(transport, MemoryTransport)
238
        self.path = path
239
        self.transport = transport
240
        if self.path in self.transport._locks:
241
            raise LockError('File %r already locked' % (self.path,))
242
        self.transport._locks[self.path] = self
243
244
    def __del__(self):
245
        # Should this warn, or actually try to cleanup?
246
        if self.transport:
247
            warn("MemoryLock %r not explicitly unlocked" % (self.path,))
248
            self.unlock()
249
250
    def unlock(self):
251
        del self.transport._locks[self.path]
252
        self.transport = None
253
254
1530.1.3 by Robert Collins
transport implementations now tested consistently.
255
class MemoryServer(Server):
256
    """Server for the MemoryTransport for testing with."""
257
258
    def setUp(self):
259
        """See bzrlib.transport.Server.setUp."""
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
260
        self._dirs = {}
261
        self._files = {}
262
        self._locks = {}
1530.1.3 by Robert Collins
transport implementations now tested consistently.
263
        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.
264
        def memory_factory(url):
265
            result = MemoryTransport(url)
266
            result._dirs = self._dirs
267
            result._files = self._files
268
            result._locks = self._locks
269
            return result
270
        register_transport(self._scheme, memory_factory)
1530.1.3 by Robert Collins
transport implementations now tested consistently.
271
272
    def tearDown(self):
273
        """See bzrlib.transport.Server.tearDown."""
274
        # unregister this server
275
276
    def get_url(self):
277
        """See bzrlib.transport.Server.get_url."""
278
        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.
279
280
281
def get_test_permutations():
282
    """Return the permutations to be used in testing."""
283
    return [(MemoryTransport, MemoryServer),
284
            ]