Merge lp:~jelmer/brz/memorytree-symlinks into lp:brz/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
Reviewer Review Type Date Requested Status
Martin Packman Approve
Review via email: mp+363704@code.launchpad.net

Commit message

Implement MemoryTree.get_symlink_target.

Description of the change

Implement MemoryTree.get_symlink_target.

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Thanks! See one inline comment about a duplicated statement.

review: Approve
Revision history for this message
Jelmer Vernooij (jelmer) :
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'breezy/git/memorytree.py'
--- breezy/git/memorytree.py 2018-11-18 00:25:19 +0000
+++ breezy/git/memorytree.py 2019-03-04 01:49:51 +0000
@@ -258,3 +258,7 @@
258 def kind(self, p):258 def kind(self, p):
259 stat_value = self._file_transport.stat(p)259 stat_value = self._file_transport.stat(p)
260 return osutils.file_kind_from_stat_mode(stat_value.st_mode)260 return osutils.file_kind_from_stat_mode(stat_value.st_mode)
261
262 def get_symlink_target(self, path):
263 with self.lock_read():
264 return self._file_transport.readlink(path)
261265
=== modified file 'breezy/memorytree.py'
--- breezy/memorytree.py 2018-11-18 00:25:19 +0000
+++ breezy/memorytree.py 2019-03-04 01:49:51 +0000
@@ -22,6 +22,7 @@
22from __future__ import absolute_import22from __future__ import absolute_import
2323
24import os24import os
25import stat
2526
26from . import (27from . import (
27 errors,28 errors,
@@ -62,7 +63,15 @@
62 with self.lock_tree_write():63 with self.lock_tree_write():
63 for f, file_id, kind in zip(files, ids, kinds):64 for f, file_id, kind in zip(files, ids, kinds):
64 if kind is None:65 if kind is None:
65 kind = 'file'66 st_mode = self._file_transport.stat(f).st_mode
67 if stat.S_ISREG(st_mode):
68 kind = 'file'
69 elif stat.S_ISLNK(st_mode):
70 kind = 'symlink'
71 elif stat.S_ISDIR(st_mode):
72 kind = 'directory'
73 else:
74 raise AssertionError('Unknown file kind')
66 if file_id is None:75 if file_id is None:
67 self._inventory.add_path(f, kind=kind)76 self._inventory.add_path(f, kind=kind)
68 else:77 else:
@@ -127,7 +136,7 @@
127 # memory tree does not support nested trees yet.136 # memory tree does not support nested trees yet.
128 return kind, None, None, None137 return kind, None, None, None
129 elif kind == 'symlink':138 elif kind == 'symlink':
130 raise NotImplementedError('symlink support')139 return kind, None, None, self._inventory[id].symlink_target
131 else:140 else:
132 raise NotImplementedError('unknown kind')141 raise NotImplementedError('unknown kind')
133142
@@ -148,8 +157,7 @@
148 return self._inventory.get_entry_by_path(path).executable157 return self._inventory.get_entry_by_path(path).executable
149158
150 def kind(self, path):159 def kind(self, path):
151 file_id = self.path2id(path)160 return self._inventory.get_entry_by_path(path).kind
152 return self._inventory[file_id].kind
153161
154 def mkdir(self, path, file_id=None):162 def mkdir(self, path, file_id=None):
155 """See MutableTree.mkdir()."""163 """See MutableTree.mkdir()."""
@@ -227,6 +235,8 @@
227 continue235 continue
228 if entry.kind == 'directory':236 if entry.kind == 'directory':
229 self._file_transport.mkdir(path)237 self._file_transport.mkdir(path)
238 elif entry.kind == 'symlink':
239 self._file_transport.symlink(entry.symlink_target, path)
230 elif entry.kind == 'file':240 elif entry.kind == 'file':
231 self._file_transport.put_file(241 self._file_transport.put_file(
232 path, self._basis_tree.get_file(path))242 path, self._basis_tree.get_file(path))
@@ -302,6 +312,10 @@
302 else:312 else:
303 raise313 raise
304314
315 def get_symlink_target(self, path):
316 with self.lock_read():
317 return self._file_transport.readlink(path)
318
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):
306 """See MutableTree.set_parent_trees()."""320 """See MutableTree.set_parent_trees()."""
307 if len(parents_list) == 0:321 if len(parents_list) == 0:
308322
=== modified file 'breezy/tests/test_memorytree.py'
--- breezy/tests/test_memorytree.py 2018-11-11 04:08:32 +0000
+++ breezy/tests/test_memorytree.py 2019-03-04 01:49:51 +0000
@@ -46,21 +46,17 @@
46 rev_id = tree.commit('first post')46 rev_id = tree.commit('first post')
47 tree.unlock()47 tree.unlock()
48 tree = MemoryTree.create_on_branch(branch)48 tree = MemoryTree.create_on_branch(branch)
49 tree.lock_read()49 with tree.lock_read():
50 self.assertEqual([rev_id], tree.get_parent_ids())50 self.assertEqual([rev_id], tree.get_parent_ids())
51 with tree.get_file('foo') as f:51 with tree.get_file('foo') as f:
52 self.assertEqual(b'contents of foo\n', f.read())52 self.assertEqual(b'contents of foo\n', f.read())
53 tree.unlock()
5453
55 def test_get_root_id(self):54 def test_get_root_id(self):
56 branch = self.make_branch('branch')55 branch = self.make_branch('branch')
57 tree = MemoryTree.create_on_branch(branch)56 tree = MemoryTree.create_on_branch(branch)
58 tree.lock_write()57 with tree.lock_write():
59 try:
60 tree.add([''])58 tree.add([''])
61 self.assertIsNot(None, tree.get_root_id())59 self.assertIsNot(None, tree.get_root_id())
62 finally:
63 tree.unlock()
6460
65 def test_lock_tree_write(self):61 def test_lock_tree_write(self):
66 """Check we can lock_tree_write and unlock MemoryTrees."""62 """Check we can lock_tree_write and unlock MemoryTrees."""
@@ -73,9 +69,8 @@
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."""
74 branch = self.make_branch('branch')70 branch = self.make_branch('branch')
75 tree = MemoryTree.create_on_branch(branch)71 tree = MemoryTree.create_on_branch(branch)
76 tree.lock_read()72 with tree.lock_read():
77 self.assertRaises(errors.ReadOnlyError, tree.lock_tree_write)73 self.assertRaises(errors.ReadOnlyError, tree.lock_tree_write)
78 tree.unlock()
7974
80 def test_lock_write(self):75 def test_lock_write(self):
81 """Check we can lock_write and unlock MemoryTrees."""76 """Check we can lock_write and unlock MemoryTrees."""
@@ -88,58 +83,63 @@
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."""
89 branch = self.make_branch('branch')84 branch = self.make_branch('branch')
90 tree = MemoryTree.create_on_branch(branch)85 tree = MemoryTree.create_on_branch(branch)
91 tree.lock_read()86 with tree.lock_read():
92 self.assertRaises(errors.ReadOnlyError, tree.lock_write)87 self.assertRaises(errors.ReadOnlyError, tree.lock_write)
93 tree.unlock()
9488
95 def test_add_with_kind(self):89 def test_add_with_kind(self):
96 branch = self.make_branch('branch')90 branch = self.make_branch('branch')
97 tree = MemoryTree.create_on_branch(branch)91 tree = MemoryTree.create_on_branch(branch)
98 tree.lock_write()92 with tree.lock_write():
99 tree.add(['', 'afile', 'adir'], None,93 tree.add(['', 'afile', 'adir'], None,
100 ['directory', 'file', 'directory'])94 ['directory', 'file', 'directory'])
101 self.assertEqual('afile', tree.id2path(tree.path2id('afile')))95 self.assertEqual('afile', tree.id2path(tree.path2id('afile')))
102 self.assertEqual('adir', tree.id2path(tree.path2id('adir')))96 self.assertEqual('adir', tree.id2path(tree.path2id('adir')))
103 self.assertFalse(tree.has_filename('afile'))97 self.assertFalse(tree.has_filename('afile'))
104 self.assertFalse(tree.has_filename('adir'))98 self.assertFalse(tree.has_filename('adir'))
105 tree.unlock()
10699
107 def test_put_new_file(self):100 def test_put_new_file(self):
108 branch = self.make_branch('branch')101 branch = self.make_branch('branch')
109 tree = MemoryTree.create_on_branch(branch)102 tree = MemoryTree.create_on_branch(branch)
110 tree.lock_write()103 with tree.lock_write():
111 tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'],104 tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'],
112 kinds=['directory', 'file'])105 kinds=['directory', 'file'])
113 tree.put_file_bytes_non_atomic('foo', b'barshoom')106 tree.put_file_bytes_non_atomic('foo', b'barshoom')
114 self.assertEqual(b'barshoom', tree.get_file('foo').read())107 with tree.get_file('foo') as f:
115 tree.unlock()108 self.assertEqual(b'barshoom', f.read())
116109
117 def test_put_existing_file(self):110 def test_put_existing_file(self):
118 branch = self.make_branch('branch')111 branch = self.make_branch('branch')
119 tree = MemoryTree.create_on_branch(branch)112 tree = MemoryTree.create_on_branch(branch)
120 tree.lock_write()113 with tree.lock_write():
121 tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'],114 tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'],
122 kinds=['directory', 'file'])115 kinds=['directory', 'file'])
123 tree.put_file_bytes_non_atomic('foo', b'first-content')116 tree.put_file_bytes_non_atomic('foo', b'first-content')
124 tree.put_file_bytes_non_atomic('foo', b'barshoom')117 tree.put_file_bytes_non_atomic('foo', b'barshoom')
125 self.assertEqual(b'barshoom', tree.get_file('foo').read())118 self.assertEqual(b'barshoom', tree.get_file('foo').read())
126 tree.unlock()
127119
128 def test_add_in_subdir(self):120 def test_add_in_subdir(self):
129 branch = self.make_branch('branch')121 branch = self.make_branch('branch')
130 tree = MemoryTree.create_on_branch(branch)122 tree = MemoryTree.create_on_branch(branch)
131 tree.lock_write()123 with tree.lock_write():
132 self.addCleanup(tree.unlock)124 tree.add([''], [b'root-id'], ['directory'])
133 tree.add([''], [b'root-id'], ['directory'])125 # Unfortunately, the only way to 'mkdir' is to call 'tree.mkdir', but
134 # Unfortunately, the only way to 'mkdir' is to call 'tree.mkdir', but126 # that *always* adds the directory as well. So if you want to create a
135 # that *always* adds the directory as well. So if you want to create a127 # file in a subdirectory, you have to split out the 'mkdir()' calls
136 # file in a subdirectory, you have to split out the 'mkdir()' calls128 # from the add and put_file_bytes_non_atomic calls. :(
137 # from the add and put_file_bytes_non_atomic calls. :(129 tree.mkdir('adir', b'dir-id')
138 tree.mkdir('adir', b'dir-id')130 tree.add(['adir/afile'], [b'file-id'], ['file'])
139 tree.add(['adir/afile'], [b'file-id'], ['file'])131 self.assertEqual('adir/afile', tree.id2path(b'file-id'))
140 self.assertEqual('adir/afile', tree.id2path(b'file-id'))132 self.assertEqual('adir', tree.id2path(b'dir-id'))
141 self.assertEqual('adir', tree.id2path(b'dir-id'))133 tree.put_file_bytes_non_atomic('adir/afile', b'barshoom')
142 tree.put_file_bytes_non_atomic('adir/afile', b'barshoom')134
135 def test_add_symlink(self):
136 branch = self.make_branch('branch')
137 tree = MemoryTree.create_on_branch(branch)
138 with tree.lock_write():
139 tree._file_transport.symlink('bar', 'foo')
140 tree.add(['', 'foo'])
141 self.assertEqual('symlink', tree.kind('foo'))
142 self.assertEqual('bar', tree.get_symlink_target('foo'))
143143
144 def test_commit_trivial(self):144 def test_commit_trivial(self):
145 """Smoke test for commit on a MemoryTree.145 """Smoke test for commit on a MemoryTree.
@@ -149,40 +149,35 @@
149 """149 """
150 branch = self.make_branch('branch')150 branch = self.make_branch('branch')
151 tree = MemoryTree.create_on_branch(branch)151 tree = MemoryTree.create_on_branch(branch)
152 tree.lock_write()152 with tree.lock_write():
153 tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'],153 tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'],
154 kinds=['directory', 'file'])154 kinds=['directory', 'file'])
155 tree.put_file_bytes_non_atomic('foo', b'barshoom')155 tree.put_file_bytes_non_atomic('foo', b'barshoom')
156 revision_id = tree.commit('message baby')156 revision_id = tree.commit('message baby')
157 # the parents list for the tree should have changed.157 # the parents list for the tree should have changed.
158 self.assertEqual([revision_id], tree.get_parent_ids())158 self.assertEqual([revision_id], tree.get_parent_ids())
159 tree.unlock()
160 # and we should have a revision that is accessible outside the tree lock159 # and we should have a revision that is accessible outside the tree lock
161 revtree = tree.branch.repository.revision_tree(revision_id)160 revtree = tree.branch.repository.revision_tree(revision_id)
162 revtree.lock_read()161 with revtree.lock_read(), revtree.get_file('foo') as f:
163 self.addCleanup(revtree.unlock)
164 with revtree.get_file('foo') as f:
165 self.assertEqual(b'barshoom', f.read())162 self.assertEqual(b'barshoom', f.read())
166163
167 def test_unversion(self):164 def test_unversion(self):
168 """Some test for unversion of a memory tree."""165 """Some test for unversion of a memory tree."""
169 branch = self.make_branch('branch')166 branch = self.make_branch('branch')
170 tree = MemoryTree.create_on_branch(branch)167 tree = MemoryTree.create_on_branch(branch)
171 tree.lock_write()168 with tree.lock_write():
172 tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'],169 tree.add(['', 'foo'], ids=[b'root-id', b'foo-id'],
173 kinds=['directory', 'file'])170 kinds=['directory', 'file'])
174 tree.unversion(['foo'])171 tree.unversion(['foo'])
175 self.assertFalse(tree.is_versioned('foo'))172 self.assertFalse(tree.is_versioned('foo'))
176 self.assertFalse(tree.has_id(b'foo-id'))173 self.assertFalse(tree.has_id(b'foo-id'))
177 tree.unlock()
178174
179 def test_last_revision(self):175 def test_last_revision(self):
180 """There should be a last revision method we can call."""176 """There should be a last revision method we can call."""
181 tree = self.make_branch_and_memory_tree('branch')177 tree = self.make_branch_and_memory_tree('branch')
182 tree.lock_write()178 with tree.lock_write():
183 tree.add('')179 tree.add('')
184 rev_id = tree.commit('first post')180 rev_id = tree.commit('first post')
185 tree.unlock()
186 self.assertEqual(rev_id, tree.last_revision())181 self.assertEqual(rev_id, tree.last_revision())
187182
188 def test_rename_file(self):183 def test_rename_file(self):
189184
=== modified file 'breezy/transport/memory.py'
--- breezy/transport/memory.py 2018-11-17 16:53:10 +0000
+++ breezy/transport/memory.py 2019-03-04 01:49:51 +0000
@@ -26,9 +26,10 @@
26from io import (26from io import (
27 BytesIO,27 BytesIO,
28 )28 )
29import itertools
29import os30import os
30import errno31import errno
31from stat import S_IFREG, S_IFDIR, S_IFLNK32from stat import S_IFREG, S_IFDIR, S_IFLNK, S_ISDIR
3233
33from .. import (34from .. import (
34 transport,35 transport,
@@ -50,20 +51,16 @@
5051
51class MemoryStat(object):52class MemoryStat(object):
5253
53 def __init__(self, size, kind, perms):54 def __init__(self, size, kind, perms=None):
54 self.st_size = size55 self.st_size = size
55 if kind == 'file':56 if not S_ISDIR(kind):
56 if perms is None:57 if perms is None:
57 perms = 0o64458 perms = 0o644
58 self.st_mode = S_IFREG | perms59 self.st_mode = kind | perms
59 elif kind == 'directory':60 else:
60 if perms is None:61 if perms is None:
61 perms = 0o75562 perms = 0o755
62 self.st_mode = S_IFDIR | perms63 self.st_mode = kind | perms
63 elif kind == 'symlink':
64 self.st_mode = S_IFLNK | 0o644
65 else:
66 raise AssertionError('unknown kind %r' % kind)
6764
6865
69class MemoryTransport(transport.Transport):66class MemoryTransport(transport.Transport):
@@ -111,7 +108,7 @@
111108
112 def append_file(self, relpath, f, mode=None):109 def append_file(self, relpath, f, mode=None):
113 """See Transport.append_file()."""110 """See Transport.append_file()."""
114 _abspath = self._abspath(relpath)111 _abspath = self._resolve_symlinks(relpath)
115 self._check_parent(_abspath)112 self._check_parent(_abspath)
116 orig_content, orig_mode = self._files.get(_abspath, (b"", None))113 orig_content, orig_mode = self._files.get(_abspath, (b"", None))
117 if mode is None:114 if mode is None:
@@ -128,16 +125,20 @@
128 def has(self, relpath):125 def has(self, relpath):
129 """See Transport.has()."""126 """See Transport.has()."""
130 _abspath = self._abspath(relpath)127 _abspath = self._abspath(relpath)
131 return ((_abspath in self._files)128 for container in (self._files, self._dirs, self._symlinks):
132 or (_abspath in self._dirs)129 if _abspath in container.keys():
133 or (_abspath in self._symlinks))130 return True
131 return False
134132
135 def delete(self, relpath):133 def delete(self, relpath):
136 """See Transport.delete()."""134 """See Transport.delete()."""
137 _abspath = self._abspath(relpath)135 _abspath = self._abspath(relpath)
138 if _abspath not in self._files:136 if _abspath in self._files:
137 del self._files[_abspath]
138 elif _abspath in self._symlinks:
139 del self._symlinks[_abspath]
140 else:
139 raise NoSuchFile(relpath)141 raise NoSuchFile(relpath)
140 del self._files[_abspath]
141142
142 def external_url(self):143 def external_url(self):
143 """See breezy.transport.Transport.external_url."""144 """See breezy.transport.Transport.external_url."""
@@ -147,7 +148,7 @@
147148
148 def get(self, relpath):149 def get(self, relpath):
149 """See Transport.get()."""150 """See Transport.get()."""
150 _abspath = self._abspath(relpath)151 _abspath = self._resolve_symlinks(relpath)
151 if _abspath not in self._files:152 if _abspath not in self._files:
152 if _abspath in self._dirs:153 if _abspath in self._dirs:
153 return LateReadError(relpath)154 return LateReadError(relpath)
@@ -157,15 +158,20 @@
157158
158 def put_file(self, relpath, f, mode=None):159 def put_file(self, relpath, f, mode=None):
159 """See Transport.put_file()."""160 """See Transport.put_file()."""
160 _abspath = self._abspath(relpath)161 _abspath = self._resolve_symlinks(relpath)
161 self._check_parent(_abspath)162 self._check_parent(_abspath)
162 raw_bytes = f.read()163 raw_bytes = f.read()
163 self._files[_abspath] = (raw_bytes, mode)164 self._files[_abspath] = (raw_bytes, mode)
164 return len(raw_bytes)165 return len(raw_bytes)
165166
167 def symlink(self, source, target):
168 _abspath = self._resolve_symlinks(target)
169 self._check_parent(_abspath)
170 self._symlinks[_abspath] = self._abspath(source)
171
166 def mkdir(self, relpath, mode=None):172 def mkdir(self, relpath, mode=None):
167 """See Transport.mkdir()."""173 """See Transport.mkdir()."""
168 _abspath = self._abspath(relpath)174 _abspath = self._resolve_symlinks(relpath)
169 self._check_parent(_abspath)175 self._check_parent(_abspath)
170 if _abspath in self._dirs:176 if _abspath in self._dirs:
171 raise FileExists(relpath)177 raise FileExists(relpath)
@@ -183,13 +189,13 @@
183 return True189 return True
184190
185 def iter_files_recursive(self):191 def iter_files_recursive(self):
186 for file in self._files:192 for file in itertools.chain(self._files, self._symlinks):
187 if file.startswith(self._cwd):193 if file.startswith(self._cwd):
188 yield urlutils.escape(file[len(self._cwd):])194 yield urlutils.escape(file[len(self._cwd):])
189195
190 def list_dir(self, relpath):196 def list_dir(self, relpath):
191 """See Transport.list_dir()."""197 """See Transport.list_dir()."""
192 _abspath = self._abspath(relpath)198 _abspath = self._resolve_symlinks(relpath)
193 if _abspath != '/' and _abspath not in self._dirs:199 if _abspath != '/' and _abspath not in self._dirs:
194 raise NoSuchFile(relpath)200 raise NoSuchFile(relpath)
195 result = []201 result = []
@@ -197,7 +203,7 @@
197 if not _abspath.endswith('/'):203 if not _abspath.endswith('/'):
198 _abspath += '/'204 _abspath += '/'
199205
200 for path_group in self._files, self._dirs:206 for path_group in self._files, self._dirs, self._symlinks:
201 for path in path_group:207 for path in path_group:
202 if path.startswith(_abspath):208 if path.startswith(_abspath):
203 trailing = path[len(_abspath):]209 trailing = path[len(_abspath):]
@@ -207,8 +213,8 @@
207213
208 def rename(self, rel_from, rel_to):214 def rename(self, rel_from, rel_to):
209 """Rename a file or directory; fail if the destination exists"""215 """Rename a file or directory; fail if the destination exists"""
210 abs_from = self._abspath(rel_from)216 abs_from = self._resolve_symlinks(rel_from)
211 abs_to = self._abspath(rel_to)217 abs_to = self._resolve_symlinks(rel_to)
212218
213 def replace(x):219 def replace(x):
214 if x == abs_from:220 if x == abs_from:
@@ -233,21 +239,25 @@
233 # fail differently depending on dict order. So work on copy, fail on239 # fail differently depending on dict order. So work on copy, fail on
234 # error on only replace dicts if all goes well.240 # error on only replace dicts if all goes well.
235 renamed_files = self._files.copy()241 renamed_files = self._files.copy()
242 renamed_symlinks = self._symlinks.copy()
236 renamed_dirs = self._dirs.copy()243 renamed_dirs = self._dirs.copy()
237 do_renames(renamed_files)244 do_renames(renamed_files)
245 do_renames(renamed_symlinks)
238 do_renames(renamed_dirs)246 do_renames(renamed_dirs)
239 # We may have been cloned so modify in place247 # We may have been cloned so modify in place
240 self._files.clear()248 self._files.clear()
241 self._files.update(renamed_files)249 self._files.update(renamed_files)
250 self._symlinks.clear()
251 self._symlinks.update(renamed_symlinks)
242 self._dirs.clear()252 self._dirs.clear()
243 self._dirs.update(renamed_dirs)253 self._dirs.update(renamed_dirs)
244254
245 def rmdir(self, relpath):255 def rmdir(self, relpath):
246 """See Transport.rmdir."""256 """See Transport.rmdir."""
247 _abspath = self._abspath(relpath)257 _abspath = self._resolve_symlinks(relpath)
248 if _abspath in self._files:258 if _abspath in self._files:
249 self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)259 self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
250 for path in self._files:260 for path in itertools.chain(self._files, self._symlinks):
251 if path.startswith(_abspath + '/'):261 if path.startswith(_abspath + '/'):
252 self._translate_error(IOError(errno.ENOTEMPTY, relpath),262 self._translate_error(IOError(errno.ENOTEMPTY, relpath),
253 relpath)263 relpath)
@@ -262,13 +272,13 @@
262 def stat(self, relpath):272 def stat(self, relpath):
263 """See Transport.stat()."""273 """See Transport.stat()."""
264 _abspath = self._abspath(relpath)274 _abspath = self._abspath(relpath)
265 if _abspath in self._files:275 if _abspath in self._files.keys():
266 return MemoryStat(len(self._files[_abspath][0]), 'file',276 return MemoryStat(len(self._files[_abspath][0]), S_IFREG,
267 self._files[_abspath][1])277 self._files[_abspath][1])
268 elif _abspath in self._dirs:278 elif _abspath in self._dirs.keys():
269 return MemoryStat(0, 'directory', self._dirs[_abspath])279 return MemoryStat(0, S_IFDIR, self._dirs[_abspath])
270 elif _abspath in self._symlinks:280 elif _abspath in self._symlinks.keys():
271 return MemoryStat(0, 'symlink', 0)281 return MemoryStat(0, S_IFLNK)
272 else:282 else:
273 raise NoSuchFile(_abspath)283 raise NoSuchFile(_abspath)
274284
@@ -280,6 +290,12 @@
280 """See Transport.lock_write()."""290 """See Transport.lock_write()."""
281 return _MemoryLock(self._abspath(relpath), self)291 return _MemoryLock(self._abspath(relpath), self)
282292
293 def _resolve_symlinks(self, relpath):
294 path = self._abspath(relpath)
295 while path in self._symlinks.keys():
296 path = self._symlinks[path]
297 return path
298
283 def _abspath(self, relpath):299 def _abspath(self, relpath):
284 """Generate an internal absolute path."""300 """Generate an internal absolute path."""
285 relpath = urlutils.unescape(relpath)301 relpath = urlutils.unescape(relpath)
@@ -336,6 +352,7 @@
336 def start_server(self):352 def start_server(self):
337 self._dirs = {'/': None}353 self._dirs = {'/': None}
338 self._files = {}354 self._files = {}
355 self._symlinks = {}
339 self._locks = {}356 self._locks = {}
340 self._scheme = "memory+%s:///" % id(self)357 self._scheme = "memory+%s:///" % id(self)
341358
@@ -344,6 +361,7 @@
344 result = memory.MemoryTransport(url)361 result = memory.MemoryTransport(url)
345 result._dirs = self._dirs362 result._dirs = self._dirs
346 result._files = self._files363 result._files = self._files
364 result._symlinks = self._symlinks
347 result._locks = self._locks365 result._locks = self._locks
348 return result366 return result
349 self._memory_factory = memory_factory367 self._memory_factory = memory_factory

Subscribers

People subscribed via source and target branches