Merge lp:~jelmer/brz/memorytree-symlinks into lp:brz

Proposed by Jelmer Vernooij
Status: Superseded
Proposed branch: lp:~jelmer/brz/memorytree-symlinks
Merge into: lp:brz
Diff against target: 509 lines (+136/-108)
3 files modified
breezy/memorytree.py (+18/-4)
breezy/tests/test_memorytree.py (+65/-70)
breezy/transport/memory.py (+53/-34)
To merge this branch: bzr merge lp:~jelmer/brz/memorytree-symlinks
Reviewer Review Type Date Requested Status
Breezy developers Pending
Review via email: mp+363285@code.launchpad.net

Description of the change

Implement MemoryTree.get_symlink_target.

To post a comment you must log in.

Preview Diff

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

Subscribers

People subscribed via source and target branches