Merge lp:~renamer-developers/renamer/1395239-reflink into lp:renamer

Proposed by Tristan Seligmann
Status: Merged
Merged at revision: 91
Proposed branch: lp:~renamer-developers/renamer/1395239-reflink
Merge into: lp:renamer
Diff against target: 173 lines (+117/-3)
4 files modified
docs/manual.rst (+7/-0)
renamer/application.py (+7/-2)
renamer/plugins/actions.py (+45/-0)
renamer/test/test_actions.py (+58/-1)
To merge this branch: bzr merge lp:~renamer-developers/renamer/1395239-reflink
Reviewer Review Type Date Requested Status
Jonathan Jacobs Approve
Review via email: mp+242553@code.launchpad.net
To post a comment you must log in.
93. By Tristan Seligmann

Fix minor error in docstring.

Revision history for this message
Jonathan Jacobs (jjacobs) wrote :

Just one minor concern, as noted in the inline comments. Please merge after resolving it.

review: Approve
94. By Tristan Seligmann

Say some stuff about ioctls.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'docs/manual.rst'
2--- docs/manual.rst 2010-10-26 14:37:34 +0000
3+++ docs/manual.rst 2014-11-24 09:37:32 +0000
4@@ -49,6 +49,13 @@
5 Create a symlink at the destination. The original file will not be moved
6 but a symlink will be created at the new location.
7
8+--reflink
9+ Create a COW copy at the destination. The file will not occupy any
10+ additional space on disk until the contents are modified. The destination
11+ must be on the same filesystem as the source, and the underlying filesystem
12+ must support the BTRFS_IOC_CLONE ioctl (as of this writing, only btrfs has
13+ this support).
14+
15 -c file, --config=file
16 Read configuration defaults from *file*. The default configuration is read
17 from *~/.renamer/renamer.conf*. See the :ref:`CONFIGURATION` section for
18
19=== modified file 'renamer/application.py'
20--- renamer/application.py 2011-12-06 10:08:13 +0000
21+++ renamer/application.py 2014-11-24 09:37:32 +0000
22@@ -24,7 +24,8 @@
23 ('one-file-system', 'x', "Don't cross filesystems."),
24 ('no-act', 'n', 'Perform a trial run with no changes made.'),
25 ('link-src', None, 'Create a symlink at the source.'),
26- ('link-dst', None, 'Create a symlink at the destination.')]
27+ ('link-dst', None, 'Create a symlink at the destination.'),
28+ ('reflink', None, 'Create a COW copy at the destination.')]
29
30
31 optParameters = [
32@@ -194,7 +195,11 @@
33 logging.msg('Skipping noop "%s"' % (src.path,), verbosity=2)
34 return
35
36- if self.options['link-dst']:
37+ if self.options['reflink']:
38+ self.changeset.do(
39+ self.changeset.newAction(u'reflink', src, dst),
40+ self.options)
41+ elif self.options['link-dst']:
42 self.changeset.do(
43 self.changeset.newAction(u'symlink', src, dst),
44 self.options)
45
46=== modified file 'renamer/plugins/actions.py'
47--- renamer/plugins/actions.py 2010-10-16 17:39:21 +0000
48+++ renamer/plugins/actions.py 2014-11-24 09:37:32 +0000
49@@ -1,3 +1,5 @@
50+from fcntl import ioctl
51+
52 from renamer import logging, util
53 from renamer.plugin import RenamingAction
54
55@@ -50,3 +52,46 @@
56 if self.dst.islink():
57 logging.msg('Symlink: Removing %s' % (self.dst.path,))
58 self.dst.remove()
59+
60+
61+# Should be:
62+# BTRFS_IOCTL_MAGIC = 0x94
63+# BTRFS_IOC_CLONE = ioctl.IOW(BTRFS_IOCTL_MAGIC, 9, INT)
64+# but Python doesn't have these.
65+BTRFS_IOC_CLONE = 0x40049409
66+
67+
68+def _reflink(src, dest):
69+ """
70+ Make a COW copy of a file.
71+
72+ @type src: L{twisted.python.filepath.FilePath}
73+ @param src: The source file.
74+
75+ @type dest: L{twisted.python.filepath.FilePath}
76+ @param dest: The destination file.
77+ """
78+ with src.open('rb') as sfile:
79+ with dest.open('wb') as dfile:
80+ ioctl(dfile.fileno(), BTRFS_IOC_CLONE, sfile.fileno())
81+
82+
83+
84+class ReflinkAction(RenamingAction):
85+ """
86+ Reflink action.
87+ """
88+ name = 'reflink'
89+
90+
91+ # IRenamingAction
92+
93+ def do(self, options):
94+ self.prepare(self.dst, options)
95+ logging.msg('Reflink: %s => %s' % (self.src.path, self.dst.path))
96+ _reflink(self.src, self.dst)
97+
98+
99+ def undo(self, options):
100+ logging.msg('Reflink: Removing %s' % (self.dst.path,))
101+ self.dst.remove()
102
103=== modified file 'renamer/test/test_actions.py'
104--- renamer/test/test_actions.py 2010-10-20 18:08:59 +0000
105+++ renamer/test/test_actions.py 2014-11-24 09:37:32 +0000
106@@ -1,5 +1,7 @@
107+from errno import ENOTTY
108+
109 from twisted.python.filepath import FilePath
110-from twisted.trial.unittest import TestCase
111+from twisted.trial.unittest import TestCase, SkipTest
112
113 from renamer import errors
114 from renamer.application import Options
115@@ -171,3 +173,58 @@
116 """
117 Undoing a symlink cannot raise L{renamer.errors.NoClobber}.
118 """
119+
120+
121+
122+class ReflinkActionTests(_ActionTestMixin, TestCase):
123+ """
124+ Tests for L{renamer.plugin.actions.ReflinkAction}.
125+ """
126+ actionType = actions.ReflinkAction
127+
128+
129+ def setUp(self):
130+ p1 = FilePath(self.mktemp())
131+ p2 = FilePath(self.mktemp())
132+ p1.setContent('foo')
133+ p2.setContent('bar')
134+ try:
135+ actions._reflink(p1, p2)
136+ except IOError, e:
137+ if e.errno == ENOTTY:
138+ raise SkipTest('Not a btrfs filesystem')
139+ raise
140+ super(ReflinkActionTests, self).setUp()
141+
142+
143+ def test_do(self):
144+ self.src.setContent('content')
145+
146+ self.assertTrue(self.src.exists())
147+ self.assertFalse(self.dst.exists())
148+
149+ action = self.createAction()
150+ action.do(self.options)
151+
152+ self.assertEquals('content', self.src.getContent())
153+ self.assertEquals('content', self.dst.getContent())
154+
155+
156+ def test_undo(self):
157+ self.src.touch()
158+ self.dst.touch()
159+
160+ self.assertTrue(self.src.exists())
161+ self.assertTrue(self.dst.exists())
162+
163+ action = self.createAction()
164+ action.undo(self.options)
165+
166+ self.assertTrue(self.src.exists())
167+ self.assertFalse(self.dst.exists())
168+
169+
170+ def test_undoClobber(self):
171+ """
172+ Undoing a reflink copy cannot raise L{renamer.errors.NoClobber}.
173+ """

Subscribers

People subscribed via source and target branches