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