Merge lp:~ian-clatworthy/bzr/filtering-revert-bug into lp:bzr/2.0
- filtering-revert-bug
- Merge into 2.0
Status: | Merged |
---|---|
Approved by: | Ian Clatworthy |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~ian-clatworthy/bzr/filtering-revert-bug |
Merge into: | lp:bzr/2.0 |
Diff against target: |
172 lines 3 files modified
NEWS (+3/-0) bzrlib/tests/per_workingtree/test_content_filters.py (+71/-22) bzrlib/transform.py (+16/-3) |
To merge this branch: | bzr merge lp:~ian-clatworthy/bzr/filtering-revert-bug |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Pool | Approve | ||
Review via email: mp+14022@code.launchpad.net |
Commit message
Description of the change
Ian Clatworthy (ian-clatworthy) wrote : | # |
Martin Pool (mbp) wrote : | # |
> This patch fixes revert on trees with content filters. I've split this bit out
> from an earlier, more complex patch. This version explains the small code
> change in a bit more detail and adds a test for reverting a rename in addition
> to the one previously proposed (reverting a delete).
This seems like a correctness improvement but also a bit kludgey. Maybe we should merge it now but look to clean it up later.
Storing the content filters in a class-wide variable seems very strange.
Surely there should be some object that takes care of doing filtering if appropriate, rather than this being inline within tt?
Martin Pool (mbp) wrote : | # |
(after irc discussion)
What I'm referring to with the comment about globals is the way the tests save and restore WorkingTree.
However, this isn't new code in this patch so I won't insist it be fixed here. I do think it would be good to add a comment since we've talked about it.
Thanks.
Martin Pool (mbp) wrote : | # |
Hi,
As part of the SRU process, we need to attribute changes in the branch
to bugs. However, there was no bug for this particular change. For
things going into the stable series, please (everyone) make sure there
is one.
--
Martin <http://
Robert Collins (lifeless) wrote : | # |
On Mon, 2009-12-14 at 08:27 +0000, Martin Pool wrote:
> Hi,
>
> As part of the SRU process, we need to attribute changes in the branch
> to bugs. However, there was no bug for this particular change. For
> things going into the stable series, please (everyone) make sure there
> is one.
I've replied on-list. I don't think this, as currently proposed, will
really do what the Ubuntu folk are looking for.
If you see https:/
that 'new upstreams' are explicitly rejected as reasons for SRU's - and
that is perhaps where the tension is coming from.
The wiki page does not explicitly say, because its written from an
Ubuntu dev perspective, but the bugs referenced there may need to be
*Ubuntu* bugs, not upstream bugs. However,
https:/
https:/
All that said, I suspect that its mostly paranoia and the changes are
not examined in the degree of detail that policy suggests might be the
case. (though I am sure they are examined).
-Rob
Preview Diff
1 | === modified file 'NEWS' | |||
2 | --- NEWS 2009-10-21 01:05:40 +0000 | |||
3 | +++ NEWS 2009-10-27 12:15:24 +0000 | |||
4 | @@ -23,6 +23,9 @@ | |||
5 | 23 | * Avoid "NoneType has no attribute st_mode" error when files disappear | 23 | * Avoid "NoneType has no attribute st_mode" error when files disappear |
6 | 24 | from a directory while it's being read. (Martin Pool, #446033) | 24 | from a directory while it's being read. (Martin Pool, #446033) |
7 | 25 | 25 | ||
8 | 26 | * Content filters are now applied correctly after revert. | ||
9 | 27 | (Ian Clatworthy) | ||
10 | 28 | |||
11 | 26 | * Diff parsing handles "Binary files differ" hunks. (Aaron Bentley, #436325) | 29 | * Diff parsing handles "Binary files differ" hunks. (Aaron Bentley, #436325) |
12 | 27 | 30 | ||
13 | 28 | * Fetching from stacked pre-2a repository via a smart server no longer | 31 | * Fetching from stacked pre-2a repository via a smart server no longer |
14 | 29 | 32 | ||
15 | === modified file 'bzrlib/tests/per_workingtree/test_content_filters.py' | |||
16 | --- bzrlib/tests/per_workingtree/test_content_filters.py 2009-08-25 04:43:21 +0000 | |||
17 | +++ bzrlib/tests/per_workingtree/test_content_filters.py 2009-10-27 12:15:24 +0000 | |||
18 | @@ -16,6 +16,8 @@ | |||
19 | 16 | 16 | ||
20 | 17 | """Tests for content filtering conformance""" | 17 | """Tests for content filtering conformance""" |
21 | 18 | 18 | ||
22 | 19 | import os | ||
23 | 20 | |||
24 | 19 | from bzrlib.filters import ContentFilter | 21 | from bzrlib.filters import ContentFilter |
25 | 20 | from bzrlib.workingtree import WorkingTree | 22 | from bzrlib.workingtree import WorkingTree |
26 | 21 | from bzrlib.tests.per_workingtree import TestCaseWithWorkingTree | 23 | from bzrlib.tests.per_workingtree import TestCaseWithWorkingTree |
27 | @@ -81,6 +83,29 @@ | |||
28 | 81 | bin_fileid = tree.path2id('file2.bin') | 83 | bin_fileid = tree.path2id('file2.bin') |
29 | 82 | return tree, txt_fileid, bin_fileid | 84 | return tree, txt_fileid, bin_fileid |
30 | 83 | 85 | ||
31 | 86 | def patch_in_content_filter(self): | ||
32 | 87 | # Patch in a custom, symmetric content filter stack | ||
33 | 88 | self.real_content_filter_stack = WorkingTree._content_filter_stack | ||
34 | 89 | def restore_real_content_filter_stack(): | ||
35 | 90 | WorkingTree._content_filter_stack = self.real_content_filter_stack | ||
36 | 91 | self.addCleanup(restore_real_content_filter_stack) | ||
37 | 92 | def _content_filter_stack(tree, path=None, file_id=None): | ||
38 | 93 | if path.endswith('.txt'): | ||
39 | 94 | return [ContentFilter(_swapcase, _swapcase)] | ||
40 | 95 | else: | ||
41 | 96 | return [] | ||
42 | 97 | WorkingTree._content_filter_stack = _content_filter_stack | ||
43 | 98 | |||
44 | 99 | def assert_basis_content(self, expected_content, branch, file_id): | ||
45 | 100 | # Note: We need to use try/finally here instead of addCleanup() | ||
46 | 101 | # as the latter leaves the read lock in place too long | ||
47 | 102 | basis = branch.basis_tree() | ||
48 | 103 | basis.lock_read() | ||
49 | 104 | try: | ||
50 | 105 | self.assertEqual(expected_content, basis.get_file_text(file_id)) | ||
51 | 106 | finally: | ||
52 | 107 | basis.unlock() | ||
53 | 108 | |||
54 | 84 | def test_symmetric_content_filtering(self): | 109 | def test_symmetric_content_filtering(self): |
55 | 85 | # test handling when read then write gives back the initial content | 110 | # test handling when read then write gives back the initial content |
56 | 86 | tree, txt_fileid, bin_fileid = self.create_cf_tree( | 111 | tree, txt_fileid, bin_fileid = self.create_cf_tree( |
57 | @@ -132,10 +157,7 @@ | |||
58 | 132 | if not source.supports_content_filtering(): | 157 | if not source.supports_content_filtering(): |
59 | 133 | return | 158 | return |
60 | 134 | self.assertFileEqual("Foo Txt", 'source/file1.txt') | 159 | self.assertFileEqual("Foo Txt", 'source/file1.txt') |
65 | 135 | basis = source.basis_tree() | 160 | self.assert_basis_content("FOO TXT", source, txt_fileid) |
62 | 136 | basis.lock_read() | ||
63 | 137 | self.addCleanup(basis.unlock) | ||
64 | 138 | self.assertEqual("FOO TXT", basis.get_file_text(txt_fileid)) | ||
66 | 139 | 161 | ||
67 | 140 | # Now branch it | 162 | # Now branch it |
68 | 141 | self.run_bzr('branch source target') | 163 | self.run_bzr('branch source target') |
69 | @@ -153,24 +175,10 @@ | |||
70 | 153 | if not source.supports_content_filtering(): | 175 | if not source.supports_content_filtering(): |
71 | 154 | return | 176 | return |
72 | 155 | self.assertFileEqual("Foo Txt", 'source/file1.txt') | 177 | self.assertFileEqual("Foo Txt", 'source/file1.txt') |
91 | 156 | basis = source.basis_tree() | 178 | self.assert_basis_content("Foo Txt", source, txt_fileid) |
92 | 157 | basis.lock_read() | 179 | |
93 | 158 | self.addCleanup(basis.unlock) | 180 | # Now patch in content filtering and branch the source |
94 | 159 | self.assertEqual("Foo Txt", basis.get_file_text(txt_fileid)) | 181 | self.patch_in_content_filter() |
77 | 160 | |||
78 | 161 | # Patch in a custom, symmetric content filter stack | ||
79 | 162 | self.real_content_filter_stack = WorkingTree._content_filter_stack | ||
80 | 163 | def restore_real_content_filter_stack(): | ||
81 | 164 | WorkingTree._content_filter_stack = self.real_content_filter_stack | ||
82 | 165 | self.addCleanup(restore_real_content_filter_stack) | ||
83 | 166 | def _content_filter_stack(tree, path=None, file_id=None): | ||
84 | 167 | if path.endswith('.txt'): | ||
85 | 168 | return [ContentFilter(_swapcase, _swapcase)] | ||
86 | 169 | else: | ||
87 | 170 | return [] | ||
88 | 171 | WorkingTree._content_filter_stack = _content_filter_stack | ||
89 | 172 | |||
90 | 173 | # Now branch it | ||
95 | 174 | self.run_bzr('branch source target') | 182 | self.run_bzr('branch source target') |
96 | 175 | target = WorkingTree.open('target') | 183 | target = WorkingTree.open('target') |
97 | 176 | # Even though the content in source and target are different | 184 | # Even though the content in source and target are different |
98 | @@ -209,3 +217,44 @@ | |||
99 | 209 | # we could give back the length of the canonical form, but in general | 217 | # we could give back the length of the canonical form, but in general |
100 | 210 | # that will be expensive to compute, so it's acceptable to just return | 218 | # that will be expensive to compute, so it's acceptable to just return |
101 | 211 | # None. | 219 | # None. |
102 | 220 | |||
103 | 221 | def test_content_filtering_applied_on_revert_delete(self): | ||
104 | 222 | # Create a source branch with content filtering | ||
105 | 223 | source, txt_fileid, bin_fileid = self.create_cf_tree( | ||
106 | 224 | txt_reader=_uppercase, txt_writer=_lowercase, dir='source') | ||
107 | 225 | if not source.supports_content_filtering(): | ||
108 | 226 | return | ||
109 | 227 | self.assertFileEqual("Foo Txt", 'source/file1.txt') | ||
110 | 228 | self.assert_basis_content("FOO TXT", source, txt_fileid) | ||
111 | 229 | |||
112 | 230 | # Now delete the file, revert it and check the content | ||
113 | 231 | os.unlink('source/file1.txt') | ||
114 | 232 | self.assertFalse(os.path.exists('source/file1.txt')) | ||
115 | 233 | source.revert(['file1.txt']) | ||
116 | 234 | self.assertTrue(os.path.exists('source/file1.txt')) | ||
117 | 235 | # Note: we don't get back exactly what was in the tree | ||
118 | 236 | # previously because lower(upper(text)) is a lossy transformation | ||
119 | 237 | self.assertFileEqual("foo txt", 'source/file1.txt') | ||
120 | 238 | |||
121 | 239 | def test_content_filtering_applied_on_revert_rename(self): | ||
122 | 240 | # Create a source branch with content filtering | ||
123 | 241 | source, txt_fileid, bin_fileid = self.create_cf_tree( | ||
124 | 242 | txt_reader=_uppercase, txt_writer=_lowercase, dir='source') | ||
125 | 243 | if not source.supports_content_filtering(): | ||
126 | 244 | return | ||
127 | 245 | self.assertFileEqual("Foo Txt", 'source/file1.txt') | ||
128 | 246 | self.assert_basis_content("FOO TXT", source, txt_fileid) | ||
129 | 247 | |||
130 | 248 | # Now modify & rename a file, revert it and check the content | ||
131 | 249 | self.build_tree_contents([ | ||
132 | 250 | ('source/file1.txt', 'Foo Txt with new content')]) | ||
133 | 251 | source.rename_one('file1.txt', 'file1.bin') | ||
134 | 252 | self.assertTrue(os.path.exists('source/file1.bin')) | ||
135 | 253 | self.assertFalse(os.path.exists('source/file1.txt')) | ||
136 | 254 | self.assertFileEqual("Foo Txt with new content", 'source/file1.bin') | ||
137 | 255 | source.revert(['file1.bin']) | ||
138 | 256 | self.assertFalse(os.path.exists('source/file1.bin')) | ||
139 | 257 | self.assertTrue(os.path.exists('source/file1.txt')) | ||
140 | 258 | # Note: we don't get back exactly what was in the tree | ||
141 | 259 | # previously because lower(upper(text)) is a lossy transformation | ||
142 | 260 | self.assertFileEqual("foo txt", 'source/file1.txt') | ||
143 | 212 | 261 | ||
144 | === modified file 'bzrlib/transform.py' | |||
145 | --- bzrlib/transform.py 2009-10-14 18:27:23 +0000 | |||
146 | +++ bzrlib/transform.py 2009-10-27 12:15:24 +0000 | |||
147 | @@ -2628,9 +2628,22 @@ | |||
148 | 2628 | tt.adjust_path(name[1], parent_trans, trans_id) | 2628 | tt.adjust_path(name[1], parent_trans, trans_id) |
149 | 2629 | if executable[0] != executable[1] and kind[1] == "file": | 2629 | if executable[0] != executable[1] and kind[1] == "file": |
150 | 2630 | tt.set_executability(executable[1], trans_id) | 2630 | tt.set_executability(executable[1], trans_id) |
154 | 2631 | for (trans_id, mode_id), bytes in target_tree.iter_files_bytes( | 2631 | if working_tree.supports_content_filtering(): |
155 | 2632 | deferred_files): | 2632 | for index, ((trans_id, mode_id), bytes) in enumerate( |
156 | 2633 | tt.create_file(bytes, trans_id, mode_id) | 2633 | target_tree.iter_files_bytes(deferred_files)): |
157 | 2634 | file_id = deferred_files[index][0] | ||
158 | 2635 | # We're reverting a tree to the target tree so using the | ||
159 | 2636 | # target tree to find the file path seems the best choice | ||
160 | 2637 | # here IMO - Ian C 27/Oct/2009 | ||
161 | 2638 | filter_tree_path = target_tree.id2path(file_id) | ||
162 | 2639 | filters = working_tree._content_filter_stack(filter_tree_path) | ||
163 | 2640 | bytes = filtered_output_bytes(bytes, filters, | ||
164 | 2641 | ContentFilterContext(filter_tree_path, working_tree)) | ||
165 | 2642 | tt.create_file(bytes, trans_id, mode_id) | ||
166 | 2643 | else: | ||
167 | 2644 | for (trans_id, mode_id), bytes in target_tree.iter_files_bytes( | ||
168 | 2645 | deferred_files): | ||
169 | 2646 | tt.create_file(bytes, trans_id, mode_id) | ||
170 | 2634 | finally: | 2647 | finally: |
171 | 2635 | if basis_tree is not None: | 2648 | if basis_tree is not None: |
172 | 2636 | basis_tree.unlock() | 2649 | basis_tree.unlock() |
This patch fixes revert on trees with content filters. I've split this bit out from an earlier, more complex patch. This version explains the small code change in a bit more detail and adds a test for reverting a rename in addition to the one previously proposed (reverting a delete).