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