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