Merge lp:~jelmer/brz/memorytree-symlinks into lp:brz/3.0
- memorytree-symlinks
- Merge into 3.0
Proposed by
Jelmer Vernooij
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Jelmer Vernooij | ||||
Approved revision: | no longer in the source branch. | ||||
Merge reported by: | The Breezy Bot | ||||
Merged at revision: | not available | ||||
Proposed branch: | lp:~jelmer/brz/memorytree-symlinks | ||||
Merge into: | lp:brz/3.0 | ||||
Diff against target: |
502 lines (+137/-106) 4 files modified
breezy/git/memorytree.py (+4/-0) breezy/memorytree.py (+18/-4) breezy/tests/test_memorytree.py (+65/-70) breezy/transport/memory.py (+50/-32) |
||||
To merge this branch: | bzr merge lp:~jelmer/brz/memorytree-symlinks | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Packman | Approve | ||
Review via email: mp+363704@code.launchpad.net |
Commit message
Implement MemoryTree.
Description of the change
Implement MemoryTree.
To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) : | # |
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
https:/
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
https:/
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'breezy/git/memorytree.py' | |||
2 | --- breezy/git/memorytree.py 2018-11-18 00:25:19 +0000 | |||
3 | +++ breezy/git/memorytree.py 2019-03-04 01:49:51 +0000 | |||
4 | @@ -258,3 +258,7 @@ | |||
5 | 258 | def kind(self, p): | 258 | def kind(self, p): |
6 | 259 | stat_value = self._file_transport.stat(p) | 259 | stat_value = self._file_transport.stat(p) |
7 | 260 | return osutils.file_kind_from_stat_mode(stat_value.st_mode) | 260 | return osutils.file_kind_from_stat_mode(stat_value.st_mode) |
8 | 261 | |||
9 | 262 | def get_symlink_target(self, path): | ||
10 | 263 | with self.lock_read(): | ||
11 | 264 | return self._file_transport.readlink(path) | ||
12 | 261 | 265 | ||
13 | === modified file 'breezy/memorytree.py' | |||
14 | --- breezy/memorytree.py 2018-11-18 00:25:19 +0000 | |||
15 | +++ breezy/memorytree.py 2019-03-04 01:49:51 +0000 | |||
16 | @@ -22,6 +22,7 @@ | |||
17 | 22 | from __future__ import absolute_import | 22 | from __future__ import absolute_import |
18 | 23 | 23 | ||
19 | 24 | import os | 24 | import os |
20 | 25 | import stat | ||
21 | 25 | 26 | ||
22 | 26 | from . import ( | 27 | from . import ( |
23 | 27 | errors, | 28 | errors, |
24 | @@ -62,7 +63,15 @@ | |||
25 | 62 | with self.lock_tree_write(): | 63 | with self.lock_tree_write(): |
26 | 63 | for f, file_id, kind in zip(files, ids, kinds): | 64 | for f, file_id, kind in zip(files, ids, kinds): |
27 | 64 | if kind is None: | 65 | if kind is None: |
29 | 65 | kind = 'file' | 66 | st_mode = self._file_transport.stat(f).st_mode |
30 | 67 | if stat.S_ISREG(st_mode): | ||
31 | 68 | kind = 'file' | ||
32 | 69 | elif stat.S_ISLNK(st_mode): | ||
33 | 70 | kind = 'symlink' | ||
34 | 71 | elif stat.S_ISDIR(st_mode): | ||
35 | 72 | kind = 'directory' | ||
36 | 73 | else: | ||
37 | 74 | raise AssertionError('Unknown file kind') | ||
38 | 66 | if file_id is None: | 75 | if file_id is None: |
39 | 67 | self._inventory.add_path(f, kind=kind) | 76 | self._inventory.add_path(f, kind=kind) |
40 | 68 | else: | 77 | else: |
41 | @@ -127,7 +136,7 @@ | |||
42 | 127 | # memory tree does not support nested trees yet. | 136 | # memory tree does not support nested trees yet. |
43 | 128 | return kind, None, None, None | 137 | return kind, None, None, None |
44 | 129 | elif kind == 'symlink': | 138 | elif kind == 'symlink': |
46 | 130 | raise NotImplementedError('symlink support') | 139 | return kind, None, None, self._inventory[id].symlink_target |
47 | 131 | else: | 140 | else: |
48 | 132 | raise NotImplementedError('unknown kind') | 141 | raise NotImplementedError('unknown kind') |
49 | 133 | 142 | ||
50 | @@ -148,8 +157,7 @@ | |||
51 | 148 | return self._inventory.get_entry_by_path(path).executable | 157 | return self._inventory.get_entry_by_path(path).executable |
52 | 149 | 158 | ||
53 | 150 | def kind(self, path): | 159 | def kind(self, path): |
56 | 151 | file_id = self.path2id(path) | 160 | return self._inventory.get_entry_by_path(path).kind |
55 | 152 | return self._inventory[file_id].kind | ||
57 | 153 | 161 | ||
58 | 154 | def mkdir(self, path, file_id=None): | 162 | def mkdir(self, path, file_id=None): |
59 | 155 | """See MutableTree.mkdir().""" | 163 | """See MutableTree.mkdir().""" |
60 | @@ -227,6 +235,8 @@ | |||
61 | 227 | continue | 235 | continue |
62 | 228 | if entry.kind == 'directory': | 236 | if entry.kind == 'directory': |
63 | 229 | self._file_transport.mkdir(path) | 237 | self._file_transport.mkdir(path) |
64 | 238 | elif entry.kind == 'symlink': | ||
65 | 239 | self._file_transport.symlink(entry.symlink_target, path) | ||
66 | 230 | elif entry.kind == 'file': | 240 | elif entry.kind == 'file': |
67 | 231 | self._file_transport.put_file( | 241 | self._file_transport.put_file( |
68 | 232 | path, self._basis_tree.get_file(path)) | 242 | path, self._basis_tree.get_file(path)) |
69 | @@ -302,6 +312,10 @@ | |||
70 | 302 | else: | 312 | else: |
71 | 303 | raise | 313 | raise |
72 | 304 | 314 | ||
73 | 315 | def get_symlink_target(self, path): | ||
74 | 316 | with self.lock_read(): | ||
75 | 317 | return self._file_transport.readlink(path) | ||
76 | 318 | |||
77 | 305 | def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False): | 319 | def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False): |
78 | 306 | """See MutableTree.set_parent_trees().""" | 320 | """See MutableTree.set_parent_trees().""" |
79 | 307 | if len(parents_list) == 0: | 321 | if len(parents_list) == 0: |
80 | 308 | 322 | ||
81 | === modified file 'breezy/tests/test_memorytree.py' | |||
82 | --- breezy/tests/test_memorytree.py 2018-11-11 04:08:32 +0000 | |||
83 | +++ breezy/tests/test_memorytree.py 2019-03-04 01:49:51 +0000 | |||
84 | @@ -46,21 +46,17 @@ | |||
85 | 46 | rev_id = tree.commit('first post') | 46 | rev_id = tree.commit('first post') |
86 | 47 | tree.unlock() | 47 | tree.unlock() |
87 | 48 | tree = MemoryTree.create_on_branch(branch) | 48 | tree = MemoryTree.create_on_branch(branch) |
93 | 49 | tree.lock_read() | 49 | with tree.lock_read(): |
94 | 50 | self.assertEqual([rev_id], tree.get_parent_ids()) | 50 | self.assertEqual([rev_id], tree.get_parent_ids()) |
95 | 51 | with tree.get_file('foo') as f: | 51 | with tree.get_file('foo') as f: |
96 | 52 | self.assertEqual(b'contents of foo\n', f.read()) | 52 | self.assertEqual(b'contents of foo\n', f.read()) |
92 | 53 | tree.unlock() | ||
97 | 54 | 53 | ||
98 | 55 | def test_get_root_id(self): | 54 | def test_get_root_id(self): |
99 | 56 | branch = self.make_branch('branch') | 55 | branch = self.make_branch('branch') |
100 | 57 | tree = MemoryTree.create_on_branch(branch) | 56 | tree = MemoryTree.create_on_branch(branch) |
103 | 58 | tree.lock_write() | 57 | with tree.lock_write(): |
102 | 59 | try: | ||
104 | 60 | tree.add(['']) | 58 | tree.add(['']) |
105 | 61 | self.assertIsNot(None, tree.get_root_id()) | 59 | self.assertIsNot(None, tree.get_root_id()) |
106 | 62 | finally: | ||
107 | 63 | tree.unlock() | ||
108 | 64 | 60 | ||
109 | 65 | def test_lock_tree_write(self): | 61 | def test_lock_tree_write(self): |
110 | 66 | """Check we can lock_tree_write and unlock MemoryTrees.""" | 62 | """Check we can lock_tree_write and unlock MemoryTrees.""" |
111 | @@ -73,9 +69,8 @@ | |||
112 | 73 | """Check that we error when trying to upgrade a read lock to write.""" | 69 | """Check that we error when trying to upgrade a read lock to write.""" |
113 | 74 | branch = self.make_branch('branch') | 70 | branch = self.make_branch('branch') |
114 | 75 | tree = MemoryTree.create_on_branch(branch) | 71 | tree = MemoryTree.create_on_branch(branch) |
118 | 76 | tree.lock_read() | 72 | with tree.lock_read(): |
119 | 77 | self.assertRaises(errors.ReadOnlyError, tree.lock_tree_write) | 73 | self.assertRaises(errors.ReadOnlyError, tree.lock_tree_write) |
117 | 78 | tree.unlock() | ||
120 | 79 | 74 | ||
121 | 80 | def test_lock_write(self): | 75 | def test_lock_write(self): |
122 | 81 | """Check we can lock_write and unlock MemoryTrees.""" | 76 | """Check we can lock_write and unlock MemoryTrees.""" |
123 | @@ -88,58 +83,63 @@ | |||
124 | 88 | """Check that we error when trying to upgrade a read lock to write.""" | 83 | """Check that we error when trying to upgrade a read lock to write.""" |
125 | 89 | branch = self.make_branch('branch') | 84 | branch = self.make_branch('branch') |
126 | 90 | tree = MemoryTree.create_on_branch(branch) | 85 | tree = MemoryTree.create_on_branch(branch) |
130 | 91 | tree.lock_read() | 86 | with tree.lock_read(): |
131 | 92 | self.assertRaises(errors.ReadOnlyError, tree.lock_write) | 87 | self.assertRaises(errors.ReadOnlyError, tree.lock_write) |
129 | 93 | tree.unlock() | ||
132 | 94 | 88 | ||
133 | 95 | def test_add_with_kind(self): | 89 | def test_add_with_kind(self): |
134 | 96 | branch = self.make_branch('branch') | 90 | branch = self.make_branch('branch') |
135 | 97 | tree = MemoryTree.create_on_branch(branch) | 91 | tree = MemoryTree.create_on_branch(branch) |
144 | 98 | tree.lock_write() | 92 | with tree.lock_write(): |
145 | 99 | tree.add(['', 'afile', 'adir'], None, | 93 | tree.add(['', 'afile', 'adir'], None, |
146 | 100 | ['directory', 'file', 'directory']) | 94 | ['directory', 'file', 'directory']) |
147 | 101 | self.assertEqual('afile', tree.id2path(tree.path2id('afile'))) | 95 | self.assertEqual('afile', tree.id2path(tree.path2id('afile'))) |
148 | 102 | self.assertEqual('adir', tree.id2path(tree.path2id('adir'))) | 96 | self.assertEqual('adir', tree.id2path(tree.path2id('adir'))) |
149 | 103 | self.assertFalse(tree.has_filename('afile')) | 97 | self.assertFalse(tree.has_filename('afile')) |
150 | 104 | self.assertFalse(tree.has_filename('adir')) | 98 | self.assertFalse(tree.has_filename('adir')) |
143 | 105 | tree.unlock() | ||
151 | 106 | 99 | ||
152 | 107 | def test_put_new_file(self): | 100 | def test_put_new_file(self): |
153 | 108 | branch = self.make_branch('branch') | 101 | branch = self.make_branch('branch') |
154 | 109 | tree = MemoryTree.create_on_branch(branch) | 102 | tree = MemoryTree.create_on_branch(branch) |
161 | 110 | tree.lock_write() | 103 | with tree.lock_write(): |
162 | 111 | tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'], | 104 | tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'], |
163 | 112 | kinds=['directory', 'file']) | 105 | kinds=['directory', 'file']) |
164 | 113 | tree.put_file_bytes_non_atomic('foo', b'barshoom') | 106 | tree.put_file_bytes_non_atomic('foo', b'barshoom') |
165 | 114 | self.assertEqual(b'barshoom', tree.get_file('foo').read()) | 107 | with tree.get_file('foo') as f: |
166 | 115 | tree.unlock() | 108 | self.assertEqual(b'barshoom', f.read()) |
167 | 116 | 109 | ||
168 | 117 | def test_put_existing_file(self): | 110 | def test_put_existing_file(self): |
169 | 118 | branch = self.make_branch('branch') | 111 | branch = self.make_branch('branch') |
170 | 119 | tree = MemoryTree.create_on_branch(branch) | 112 | tree = MemoryTree.create_on_branch(branch) |
178 | 120 | tree.lock_write() | 113 | with tree.lock_write(): |
179 | 121 | tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'], | 114 | tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'], |
180 | 122 | kinds=['directory', 'file']) | 115 | kinds=['directory', 'file']) |
181 | 123 | tree.put_file_bytes_non_atomic('foo', b'first-content') | 116 | tree.put_file_bytes_non_atomic('foo', b'first-content') |
182 | 124 | tree.put_file_bytes_non_atomic('foo', b'barshoom') | 117 | tree.put_file_bytes_non_atomic('foo', b'barshoom') |
183 | 125 | self.assertEqual(b'barshoom', tree.get_file('foo').read()) | 118 | self.assertEqual(b'barshoom', tree.get_file('foo').read()) |
177 | 126 | tree.unlock() | ||
184 | 127 | 119 | ||
185 | 128 | def test_add_in_subdir(self): | 120 | def test_add_in_subdir(self): |
186 | 129 | branch = self.make_branch('branch') | 121 | branch = self.make_branch('branch') |
187 | 130 | tree = MemoryTree.create_on_branch(branch) | 122 | tree = MemoryTree.create_on_branch(branch) |
200 | 131 | tree.lock_write() | 123 | with tree.lock_write(): |
201 | 132 | self.addCleanup(tree.unlock) | 124 | tree.add([''], [b'root-id'], ['directory']) |
202 | 133 | tree.add([''], [b'root-id'], ['directory']) | 125 | # Unfortunately, the only way to 'mkdir' is to call 'tree.mkdir', but |
203 | 134 | # Unfortunately, the only way to 'mkdir' is to call 'tree.mkdir', but | 126 | # that *always* adds the directory as well. So if you want to create a |
204 | 135 | # that *always* adds the directory as well. So if you want to create a | 127 | # file in a subdirectory, you have to split out the 'mkdir()' calls |
205 | 136 | # file in a subdirectory, you have to split out the 'mkdir()' calls | 128 | # from the add and put_file_bytes_non_atomic calls. :( |
206 | 137 | # from the add and put_file_bytes_non_atomic calls. :( | 129 | tree.mkdir('adir', b'dir-id') |
207 | 138 | tree.mkdir('adir', b'dir-id') | 130 | tree.add(['adir/afile'], [b'file-id'], ['file']) |
208 | 139 | tree.add(['adir/afile'], [b'file-id'], ['file']) | 131 | self.assertEqual('adir/afile', tree.id2path(b'file-id')) |
209 | 140 | self.assertEqual('adir/afile', tree.id2path(b'file-id')) | 132 | self.assertEqual('adir', tree.id2path(b'dir-id')) |
210 | 141 | self.assertEqual('adir', tree.id2path(b'dir-id')) | 133 | tree.put_file_bytes_non_atomic('adir/afile', b'barshoom') |
211 | 142 | tree.put_file_bytes_non_atomic('adir/afile', b'barshoom') | 134 | |
212 | 135 | def test_add_symlink(self): | ||
213 | 136 | branch = self.make_branch('branch') | ||
214 | 137 | tree = MemoryTree.create_on_branch(branch) | ||
215 | 138 | with tree.lock_write(): | ||
216 | 139 | tree._file_transport.symlink('bar', 'foo') | ||
217 | 140 | tree.add(['', 'foo']) | ||
218 | 141 | self.assertEqual('symlink', tree.kind('foo')) | ||
219 | 142 | self.assertEqual('bar', tree.get_symlink_target('foo')) | ||
220 | 143 | 143 | ||
221 | 144 | def test_commit_trivial(self): | 144 | def test_commit_trivial(self): |
222 | 145 | """Smoke test for commit on a MemoryTree. | 145 | """Smoke test for commit on a MemoryTree. |
223 | @@ -149,40 +149,35 @@ | |||
224 | 149 | """ | 149 | """ |
225 | 150 | branch = self.make_branch('branch') | 150 | branch = self.make_branch('branch') |
226 | 151 | tree = MemoryTree.create_on_branch(branch) | 151 | tree = MemoryTree.create_on_branch(branch) |
235 | 152 | tree.lock_write() | 152 | with tree.lock_write(): |
236 | 153 | tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'], | 153 | tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'], |
237 | 154 | kinds=['directory', 'file']) | 154 | kinds=['directory', 'file']) |
238 | 155 | tree.put_file_bytes_non_atomic('foo', b'barshoom') | 155 | tree.put_file_bytes_non_atomic('foo', b'barshoom') |
239 | 156 | revision_id = tree.commit('message baby') | 156 | revision_id = tree.commit('message baby') |
240 | 157 | # the parents list for the tree should have changed. | 157 | # the parents list for the tree should have changed. |
241 | 158 | self.assertEqual([revision_id], tree.get_parent_ids()) | 158 | self.assertEqual([revision_id], tree.get_parent_ids()) |
234 | 159 | tree.unlock() | ||
242 | 160 | # and we should have a revision that is accessible outside the tree lock | 159 | # and we should have a revision that is accessible outside the tree lock |
243 | 161 | revtree = tree.branch.repository.revision_tree(revision_id) | 160 | revtree = tree.branch.repository.revision_tree(revision_id) |
247 | 162 | revtree.lock_read() | 161 | with revtree.lock_read(), revtree.get_file('foo') as f: |
245 | 163 | self.addCleanup(revtree.unlock) | ||
246 | 164 | with revtree.get_file('foo') as f: | ||
248 | 165 | self.assertEqual(b'barshoom', f.read()) | 162 | self.assertEqual(b'barshoom', f.read()) |
249 | 166 | 163 | ||
250 | 167 | def test_unversion(self): | 164 | def test_unversion(self): |
251 | 168 | """Some test for unversion of a memory tree.""" | 165 | """Some test for unversion of a memory tree.""" |
252 | 169 | branch = self.make_branch('branch') | 166 | branch = self.make_branch('branch') |
253 | 170 | tree = MemoryTree.create_on_branch(branch) | 167 | tree = MemoryTree.create_on_branch(branch) |
261 | 171 | tree.lock_write() | 168 | with tree.lock_write(): |
262 | 172 | tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'], | 169 | tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'], |
263 | 173 | kinds=['directory', 'file']) | 170 | kinds=['directory', 'file']) |
264 | 174 | tree.unversion(['foo']) | 171 | tree.unversion(['foo']) |
265 | 175 | self.assertFalse(tree.is_versioned('foo')) | 172 | self.assertFalse(tree.is_versioned('foo')) |
266 | 176 | self.assertFalse(tree.has_id(b'foo-id')) | 173 | self.assertFalse(tree.has_id(b'foo-id')) |
260 | 177 | tree.unlock() | ||
267 | 178 | 174 | ||
268 | 179 | def test_last_revision(self): | 175 | def test_last_revision(self): |
269 | 180 | """There should be a last revision method we can call.""" | 176 | """There should be a last revision method we can call.""" |
270 | 181 | tree = self.make_branch_and_memory_tree('branch') | 177 | tree = self.make_branch_and_memory_tree('branch') |
275 | 182 | tree.lock_write() | 178 | with tree.lock_write(): |
276 | 183 | tree.add('') | 179 | tree.add('') |
277 | 184 | rev_id = tree.commit('first post') | 180 | rev_id = tree.commit('first post') |
274 | 185 | tree.unlock() | ||
278 | 186 | self.assertEqual(rev_id, tree.last_revision()) | 181 | self.assertEqual(rev_id, tree.last_revision()) |
279 | 187 | 182 | ||
280 | 188 | def test_rename_file(self): | 183 | def test_rename_file(self): |
281 | 189 | 184 | ||
282 | === modified file 'breezy/transport/memory.py' | |||
283 | --- breezy/transport/memory.py 2018-11-17 16:53:10 +0000 | |||
284 | +++ breezy/transport/memory.py 2019-03-04 01:49:51 +0000 | |||
285 | @@ -26,9 +26,10 @@ | |||
286 | 26 | from io import ( | 26 | from io import ( |
287 | 27 | BytesIO, | 27 | BytesIO, |
288 | 28 | ) | 28 | ) |
289 | 29 | import itertools | ||
290 | 29 | import os | 30 | import os |
291 | 30 | import errno | 31 | import errno |
293 | 31 | from stat import S_IFREG, S_IFDIR, S_IFLNK | 32 | from stat import S_IFREG, S_IFDIR, S_IFLNK, S_ISDIR |
294 | 32 | 33 | ||
295 | 33 | from .. import ( | 34 | from .. import ( |
296 | 34 | transport, | 35 | transport, |
297 | @@ -50,20 +51,16 @@ | |||
298 | 50 | 51 | ||
299 | 51 | class MemoryStat(object): | 52 | class MemoryStat(object): |
300 | 52 | 53 | ||
302 | 53 | def __init__(self, size, kind, perms): | 54 | def __init__(self, size, kind, perms=None): |
303 | 54 | self.st_size = size | 55 | self.st_size = size |
305 | 55 | if kind == 'file': | 56 | if not S_ISDIR(kind): |
306 | 56 | if perms is None: | 57 | if perms is None: |
307 | 57 | perms = 0o644 | 58 | perms = 0o644 |
310 | 58 | self.st_mode = S_IFREG | perms | 59 | self.st_mode = kind | perms |
311 | 59 | elif kind == 'directory': | 60 | else: |
312 | 60 | if perms is None: | 61 | if perms is None: |
313 | 61 | perms = 0o755 | 62 | perms = 0o755 |
319 | 62 | self.st_mode = S_IFDIR | perms | 63 | self.st_mode = kind | perms |
315 | 63 | elif kind == 'symlink': | ||
316 | 64 | self.st_mode = S_IFLNK | 0o644 | ||
317 | 65 | else: | ||
318 | 66 | raise AssertionError('unknown kind %r' % kind) | ||
320 | 67 | 64 | ||
321 | 68 | 65 | ||
322 | 69 | class MemoryTransport(transport.Transport): | 66 | class MemoryTransport(transport.Transport): |
323 | @@ -111,7 +108,7 @@ | |||
324 | 111 | 108 | ||
325 | 112 | def append_file(self, relpath, f, mode=None): | 109 | def append_file(self, relpath, f, mode=None): |
326 | 113 | """See Transport.append_file().""" | 110 | """See Transport.append_file().""" |
328 | 114 | _abspath = self._abspath(relpath) | 111 | _abspath = self._resolve_symlinks(relpath) |
329 | 115 | self._check_parent(_abspath) | 112 | self._check_parent(_abspath) |
330 | 116 | orig_content, orig_mode = self._files.get(_abspath, (b"", None)) | 113 | orig_content, orig_mode = self._files.get(_abspath, (b"", None)) |
331 | 117 | if mode is None: | 114 | if mode is None: |
332 | @@ -128,16 +125,20 @@ | |||
333 | 128 | def has(self, relpath): | 125 | def has(self, relpath): |
334 | 129 | """See Transport.has().""" | 126 | """See Transport.has().""" |
335 | 130 | _abspath = self._abspath(relpath) | 127 | _abspath = self._abspath(relpath) |
339 | 131 | return ((_abspath in self._files) | 128 | for container in (self._files, self._dirs, self._symlinks): |
340 | 132 | or (_abspath in self._dirs) | 129 | if _abspath in container.keys(): |
341 | 133 | or (_abspath in self._symlinks)) | 130 | return True |
342 | 131 | return False | ||
343 | 134 | 132 | ||
344 | 135 | def delete(self, relpath): | 133 | def delete(self, relpath): |
345 | 136 | """See Transport.delete().""" | 134 | """See Transport.delete().""" |
346 | 137 | _abspath = self._abspath(relpath) | 135 | _abspath = self._abspath(relpath) |
348 | 138 | if _abspath not in self._files: | 136 | if _abspath in self._files: |
349 | 137 | del self._files[_abspath] | ||
350 | 138 | elif _abspath in self._symlinks: | ||
351 | 139 | del self._symlinks[_abspath] | ||
352 | 140 | else: | ||
353 | 139 | raise NoSuchFile(relpath) | 141 | raise NoSuchFile(relpath) |
354 | 140 | del self._files[_abspath] | ||
355 | 141 | 142 | ||
356 | 142 | def external_url(self): | 143 | def external_url(self): |
357 | 143 | """See breezy.transport.Transport.external_url.""" | 144 | """See breezy.transport.Transport.external_url.""" |
358 | @@ -147,7 +148,7 @@ | |||
359 | 147 | 148 | ||
360 | 148 | def get(self, relpath): | 149 | def get(self, relpath): |
361 | 149 | """See Transport.get().""" | 150 | """See Transport.get().""" |
363 | 150 | _abspath = self._abspath(relpath) | 151 | _abspath = self._resolve_symlinks(relpath) |
364 | 151 | if _abspath not in self._files: | 152 | if _abspath not in self._files: |
365 | 152 | if _abspath in self._dirs: | 153 | if _abspath in self._dirs: |
366 | 153 | return LateReadError(relpath) | 154 | return LateReadError(relpath) |
367 | @@ -157,15 +158,20 @@ | |||
368 | 157 | 158 | ||
369 | 158 | def put_file(self, relpath, f, mode=None): | 159 | def put_file(self, relpath, f, mode=None): |
370 | 159 | """See Transport.put_file().""" | 160 | """See Transport.put_file().""" |
372 | 160 | _abspath = self._abspath(relpath) | 161 | _abspath = self._resolve_symlinks(relpath) |
373 | 161 | self._check_parent(_abspath) | 162 | self._check_parent(_abspath) |
374 | 162 | raw_bytes = f.read() | 163 | raw_bytes = f.read() |
375 | 163 | self._files[_abspath] = (raw_bytes, mode) | 164 | self._files[_abspath] = (raw_bytes, mode) |
376 | 164 | return len(raw_bytes) | 165 | return len(raw_bytes) |
377 | 165 | 166 | ||
378 | 167 | def symlink(self, source, target): | ||
379 | 168 | _abspath = self._resolve_symlinks(target) | ||
380 | 169 | self._check_parent(_abspath) | ||
381 | 170 | self._symlinks[_abspath] = self._abspath(source) | ||
382 | 171 | |||
383 | 166 | def mkdir(self, relpath, mode=None): | 172 | def mkdir(self, relpath, mode=None): |
384 | 167 | """See Transport.mkdir().""" | 173 | """See Transport.mkdir().""" |
386 | 168 | _abspath = self._abspath(relpath) | 174 | _abspath = self._resolve_symlinks(relpath) |
387 | 169 | self._check_parent(_abspath) | 175 | self._check_parent(_abspath) |
388 | 170 | if _abspath in self._dirs: | 176 | if _abspath in self._dirs: |
389 | 171 | raise FileExists(relpath) | 177 | raise FileExists(relpath) |
390 | @@ -183,13 +189,13 @@ | |||
391 | 183 | return True | 189 | return True |
392 | 184 | 190 | ||
393 | 185 | def iter_files_recursive(self): | 191 | def iter_files_recursive(self): |
395 | 186 | for file in self._files: | 192 | for file in itertools.chain(self._files, self._symlinks): |
396 | 187 | if file.startswith(self._cwd): | 193 | if file.startswith(self._cwd): |
397 | 188 | yield urlutils.escape(file[len(self._cwd):]) | 194 | yield urlutils.escape(file[len(self._cwd):]) |
398 | 189 | 195 | ||
399 | 190 | def list_dir(self, relpath): | 196 | def list_dir(self, relpath): |
400 | 191 | """See Transport.list_dir().""" | 197 | """See Transport.list_dir().""" |
402 | 192 | _abspath = self._abspath(relpath) | 198 | _abspath = self._resolve_symlinks(relpath) |
403 | 193 | if _abspath != '/' and _abspath not in self._dirs: | 199 | if _abspath != '/' and _abspath not in self._dirs: |
404 | 194 | raise NoSuchFile(relpath) | 200 | raise NoSuchFile(relpath) |
405 | 195 | result = [] | 201 | result = [] |
406 | @@ -197,7 +203,7 @@ | |||
407 | 197 | if not _abspath.endswith('/'): | 203 | if not _abspath.endswith('/'): |
408 | 198 | _abspath += '/' | 204 | _abspath += '/' |
409 | 199 | 205 | ||
411 | 200 | for path_group in self._files, self._dirs: | 206 | for path_group in self._files, self._dirs, self._symlinks: |
412 | 201 | for path in path_group: | 207 | for path in path_group: |
413 | 202 | if path.startswith(_abspath): | 208 | if path.startswith(_abspath): |
414 | 203 | trailing = path[len(_abspath):] | 209 | trailing = path[len(_abspath):] |
415 | @@ -207,8 +213,8 @@ | |||
416 | 207 | 213 | ||
417 | 208 | def rename(self, rel_from, rel_to): | 214 | def rename(self, rel_from, rel_to): |
418 | 209 | """Rename a file or directory; fail if the destination exists""" | 215 | """Rename a file or directory; fail if the destination exists""" |
421 | 210 | abs_from = self._abspath(rel_from) | 216 | abs_from = self._resolve_symlinks(rel_from) |
422 | 211 | abs_to = self._abspath(rel_to) | 217 | abs_to = self._resolve_symlinks(rel_to) |
423 | 212 | 218 | ||
424 | 213 | def replace(x): | 219 | def replace(x): |
425 | 214 | if x == abs_from: | 220 | if x == abs_from: |
426 | @@ -233,21 +239,25 @@ | |||
427 | 233 | # fail differently depending on dict order. So work on copy, fail on | 239 | # fail differently depending on dict order. So work on copy, fail on |
428 | 234 | # error on only replace dicts if all goes well. | 240 | # error on only replace dicts if all goes well. |
429 | 235 | renamed_files = self._files.copy() | 241 | renamed_files = self._files.copy() |
430 | 242 | renamed_symlinks = self._symlinks.copy() | ||
431 | 236 | renamed_dirs = self._dirs.copy() | 243 | renamed_dirs = self._dirs.copy() |
432 | 237 | do_renames(renamed_files) | 244 | do_renames(renamed_files) |
433 | 245 | do_renames(renamed_symlinks) | ||
434 | 238 | do_renames(renamed_dirs) | 246 | do_renames(renamed_dirs) |
435 | 239 | # We may have been cloned so modify in place | 247 | # We may have been cloned so modify in place |
436 | 240 | self._files.clear() | 248 | self._files.clear() |
437 | 241 | self._files.update(renamed_files) | 249 | self._files.update(renamed_files) |
438 | 250 | self._symlinks.clear() | ||
439 | 251 | self._symlinks.update(renamed_symlinks) | ||
440 | 242 | self._dirs.clear() | 252 | self._dirs.clear() |
441 | 243 | self._dirs.update(renamed_dirs) | 253 | self._dirs.update(renamed_dirs) |
442 | 244 | 254 | ||
443 | 245 | def rmdir(self, relpath): | 255 | def rmdir(self, relpath): |
444 | 246 | """See Transport.rmdir.""" | 256 | """See Transport.rmdir.""" |
446 | 247 | _abspath = self._abspath(relpath) | 257 | _abspath = self._resolve_symlinks(relpath) |
447 | 248 | if _abspath in self._files: | 258 | if _abspath in self._files: |
448 | 249 | self._translate_error(IOError(errno.ENOTDIR, relpath), relpath) | 259 | self._translate_error(IOError(errno.ENOTDIR, relpath), relpath) |
450 | 250 | for path in self._files: | 260 | for path in itertools.chain(self._files, self._symlinks): |
451 | 251 | if path.startswith(_abspath + '/'): | 261 | if path.startswith(_abspath + '/'): |
452 | 252 | self._translate_error(IOError(errno.ENOTEMPTY, relpath), | 262 | self._translate_error(IOError(errno.ENOTEMPTY, relpath), |
453 | 253 | relpath) | 263 | relpath) |
454 | @@ -262,13 +272,13 @@ | |||
455 | 262 | def stat(self, relpath): | 272 | def stat(self, relpath): |
456 | 263 | """See Transport.stat().""" | 273 | """See Transport.stat().""" |
457 | 264 | _abspath = self._abspath(relpath) | 274 | _abspath = self._abspath(relpath) |
460 | 265 | if _abspath in self._files: | 275 | if _abspath in self._files.keys(): |
461 | 266 | return MemoryStat(len(self._files[_abspath][0]), 'file', | 276 | return MemoryStat(len(self._files[_abspath][0]), S_IFREG, |
462 | 267 | self._files[_abspath][1]) | 277 | self._files[_abspath][1]) |
467 | 268 | elif _abspath in self._dirs: | 278 | elif _abspath in self._dirs.keys(): |
468 | 269 | return MemoryStat(0, 'directory', self._dirs[_abspath]) | 279 | return MemoryStat(0, S_IFDIR, self._dirs[_abspath]) |
469 | 270 | elif _abspath in self._symlinks: | 280 | elif _abspath in self._symlinks.keys(): |
470 | 271 | return MemoryStat(0, 'symlink', 0) | 281 | return MemoryStat(0, S_IFLNK) |
471 | 272 | else: | 282 | else: |
472 | 273 | raise NoSuchFile(_abspath) | 283 | raise NoSuchFile(_abspath) |
473 | 274 | 284 | ||
474 | @@ -280,6 +290,12 @@ | |||
475 | 280 | """See Transport.lock_write().""" | 290 | """See Transport.lock_write().""" |
476 | 281 | return _MemoryLock(self._abspath(relpath), self) | 291 | return _MemoryLock(self._abspath(relpath), self) |
477 | 282 | 292 | ||
478 | 293 | def _resolve_symlinks(self, relpath): | ||
479 | 294 | path = self._abspath(relpath) | ||
480 | 295 | while path in self._symlinks.keys(): | ||
481 | 296 | path = self._symlinks[path] | ||
482 | 297 | return path | ||
483 | 298 | |||
484 | 283 | def _abspath(self, relpath): | 299 | def _abspath(self, relpath): |
485 | 284 | """Generate an internal absolute path.""" | 300 | """Generate an internal absolute path.""" |
486 | 285 | relpath = urlutils.unescape(relpath) | 301 | relpath = urlutils.unescape(relpath) |
487 | @@ -336,6 +352,7 @@ | |||
488 | 336 | def start_server(self): | 352 | def start_server(self): |
489 | 337 | self._dirs = {'/': None} | 353 | self._dirs = {'/': None} |
490 | 338 | self._files = {} | 354 | self._files = {} |
491 | 355 | self._symlinks = {} | ||
492 | 339 | self._locks = {} | 356 | self._locks = {} |
493 | 340 | self._scheme = "memory+%s:///" % id(self) | 357 | self._scheme = "memory+%s:///" % id(self) |
494 | 341 | 358 | ||
495 | @@ -344,6 +361,7 @@ | |||
496 | 344 | result = memory.MemoryTransport(url) | 361 | result = memory.MemoryTransport(url) |
497 | 345 | result._dirs = self._dirs | 362 | result._dirs = self._dirs |
498 | 346 | result._files = self._files | 363 | result._files = self._files |
499 | 364 | result._symlinks = self._symlinks | ||
500 | 347 | result._locks = self._locks | 365 | result._locks = self._locks |
501 | 348 | return result | 366 | return result |
502 | 349 | self._memory_factory = memory_factory | 367 | self._memory_factory = memory_factory |
Thanks! See one inline comment about a duplicated statement.