Merge lp:~ian-clatworthy/bzr/eol-st-ci-fix into lp:~bzr/bzr/trunk-old
- eol-st-ci-fix
- Merge into trunk-old
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~ian-clatworthy/bzr/eol-st-ci-fix |
Merge into: | lp:~bzr/bzr/trunk-old |
Diff against target: | 555 lines |
To merge this branch: | bzr merge lp:~ian-clatworthy/bzr/eol-st-ci-fix |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John A Meinel | Approve | ||
Review via email: mp+7269@code.launchpad.net |
Commit message
Description of the change
Ian Clatworthy (ian-clatworthy) wrote : | # |
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Ian Clatworthy wrote:
> Ian Clatworthy has proposed merging lp:~ian-clatworthy/bzr/eol-st-ci-fix into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> This patch fixes some very bad bugs in content filtering. There are actually 2 separate issues fixed:
>
> 1. Branching from a non-content-
>
> 2. Status detection was relying on size matching to determine equivalence when that assumption doesn't hold in the presence of content filtering.
>
> Both code changes are quite straightforward, though the latter one requires fixes in both Python and Pyrex code. Testing all of this is far more complex though. I've added a heap more units tests and I'm comfortable that status on content filtered trees now has pretty good coverage.
>
> I'm yet to add detailed tests for the branch/commit issue, because the monkey-patching approach used to test status isn't at the right level to work for branch. I have tested it manually though and one of the bug reporters (Frits) has confirmed that the code fixes works correctly for him. Given the related code change - switching off an optional optimisation - is very straight forward, I think this patch can land as is for 1.16rc1.
>
> FWIW, my plan is to add those additional tests soon. (Some pending changes to the monkey-patching used here to fix some other bugs will make that easier, but they aren't ready to go, and this really needs to be landed for content filtering to be unbroken for users.)
>
+ # Check the sha. We can't just rely on the size as
+ # content filtering may mean differ sizes actually
+ # map to the same content
+ if link_or_sha1 is None:
^- It would be nice if this was only done when there were actually
filters in play, rather than disabling the size check universally.
This change will cause us to sha1 the content of everything we are
committing 2 times. Once during iter_changes to make sure it really has
changed, and then once during the insert-
its final sha, and ensure that it really has changed.
Note that ideally we would have an iter_changes that could provide
"maybe changed" and *never* sha content. Perhaps we can address that
when we re-visit dirstate. (So I'm saying it is probably ok for now, but
it is not the way we want to leave the code in the long run.)
...
# Note: do NOT move this logic up higher - using the
basis from
# the accelerator tree is still desirable because that
can save
# a minute or more of processing on large trees!
+ # The original tree may not have the same content filters
+ # applied so we can't safely build the inventory delta from
+ # the source tree.
if wt.supports_
+ delta_from_tree = False
+ else:
+ delta...
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2009-06-11 00:47:41 +0000 |
3 | +++ NEWS 2009-06-11 04:35:14 +0000 |
4 | @@ -81,6 +81,9 @@ |
5 | the fetched revisions, not in the stacked-on ancestry. |
6 | (John Arbash Meinel) |
7 | |
8 | +* Fix status and commit to work with content filtered trees, addressing |
9 | + numerous bad bugs with line-ending support. (Ian Clatworthy, #362030) |
10 | + |
11 | * Fix problem of "directory not empty" when contending for a lock over |
12 | sftp. (Martin Pool, #340352) |
13 | |
14 | |
15 | === modified file 'bzrlib/_dirstate_helpers_c.pyx' |
16 | --- bzrlib/_dirstate_helpers_c.pyx 2009-03-23 14:59:43 +0000 |
17 | +++ bzrlib/_dirstate_helpers_c.pyx 2009-06-11 04:35:14 +0000 |
18 | @@ -1140,19 +1140,17 @@ |
19 | if source_minikind != c'f': |
20 | content_change = 1 |
21 | else: |
22 | - # If the size is the same, check the sha: |
23 | - if target_details[2] == source_details[2]: |
24 | - if link_or_sha1 is None: |
25 | - # Stat cache miss: |
26 | - statvalue, link_or_sha1 = \ |
27 | - self.state._sha1_provider.stat_and_sha1( |
28 | - path_info[4]) |
29 | - self.state._observed_sha1(entry, link_or_sha1, |
30 | - statvalue) |
31 | - content_change = (link_or_sha1 != source_details[1]) |
32 | - else: |
33 | - # Size changed, so must be different |
34 | - content_change = 1 |
35 | + # Check the sha. We can't just rely on the size as |
36 | + # content filtering may mean differ sizes actually |
37 | + # map to the same content |
38 | + if link_or_sha1 is None: |
39 | + # Stat cache miss: |
40 | + statvalue, link_or_sha1 = \ |
41 | + self.state._sha1_provider.stat_and_sha1( |
42 | + path_info[4]) |
43 | + self.state._observed_sha1(entry, link_or_sha1, |
44 | + statvalue) |
45 | + content_change = (link_or_sha1 != source_details[1]) |
46 | # Target details is updated at update_entry time |
47 | if self.use_filesystem_for_exec: |
48 | # We don't need S_ISREG here, because we are sure |
49 | |
50 | === modified file 'bzrlib/builtins.py' |
51 | --- bzrlib/builtins.py 2009-06-10 03:56:49 +0000 |
52 | +++ bzrlib/builtins.py 2009-06-11 04:35:14 +0000 |
53 | @@ -1112,6 +1112,9 @@ |
54 | |
55 | accelerator_tree, br_from = bzrdir.BzrDir.open_tree_or_branch( |
56 | from_location) |
57 | + if (accelerator_tree is not None and |
58 | + accelerator_tree.supports_content_filtering()): |
59 | + accelerator_tree = None |
60 | revision = _get_one_revision('branch', revision) |
61 | br_from.lock_read() |
62 | try: |
63 | |
64 | === modified file 'bzrlib/dirstate.py' |
65 | --- bzrlib/dirstate.py 2009-05-06 05:36:28 +0000 |
66 | +++ bzrlib/dirstate.py 2009-06-11 04:35:14 +0000 |
67 | @@ -3062,19 +3062,17 @@ |
68 | if source_minikind != 'f': |
69 | content_change = True |
70 | else: |
71 | - # If the size is the same, check the sha: |
72 | - if target_details[2] == source_details[2]: |
73 | - if link_or_sha1 is None: |
74 | - # Stat cache miss: |
75 | - statvalue, link_or_sha1 = \ |
76 | - self.state._sha1_provider.stat_and_sha1( |
77 | - path_info[4]) |
78 | - self.state._observed_sha1(entry, link_or_sha1, |
79 | - statvalue) |
80 | - content_change = (link_or_sha1 != source_details[1]) |
81 | - else: |
82 | - # Size changed, so must be different |
83 | - content_change = True |
84 | + # Check the sha. We can't just rely on the size as |
85 | + # content filtering may mean differ sizes actually |
86 | + # map to the same content |
87 | + if link_or_sha1 is None: |
88 | + # Stat cache miss: |
89 | + statvalue, link_or_sha1 = \ |
90 | + self.state._sha1_provider.stat_and_sha1( |
91 | + path_info[4]) |
92 | + self.state._observed_sha1(entry, link_or_sha1, |
93 | + statvalue) |
94 | + content_change = (link_or_sha1 != source_details[1]) |
95 | # Target details is updated at update_entry time |
96 | if self.use_filesystem_for_exec: |
97 | # We don't need S_ISREG here, because we are sure |
98 | |
99 | === modified file 'bzrlib/tests/workingtree_implementations/test_content_filters.py' |
100 | --- bzrlib/tests/workingtree_implementations/test_content_filters.py 2009-03-23 14:59:43 +0000 |
101 | +++ bzrlib/tests/workingtree_implementations/test_content_filters.py 2009-06-11 04:35:14 +0000 |
102 | @@ -17,6 +17,7 @@ |
103 | """Tests for content filtering conformance""" |
104 | |
105 | from bzrlib.filters import ContentFilter |
106 | +from bzrlib.workingtree import WorkingTree |
107 | from bzrlib.tests.workingtree_implementations import TestCaseWithWorkingTree |
108 | |
109 | |
110 | @@ -44,8 +45,8 @@ |
111 | |
112 | class TestWorkingTreeWithContentFilters(TestCaseWithWorkingTree): |
113 | |
114 | - def create_cf_tree(self, txt_reader, txt_writer): |
115 | - tree = self.make_branch_and_tree('.') |
116 | + def create_cf_tree(self, txt_reader, txt_writer, dir='.'): |
117 | + tree = self.make_branch_and_tree(dir) |
118 | def _content_filter_stack(path=None, file_id=None): |
119 | if path.endswith('.txt'): |
120 | return [ContentFilter(txt_reader, txt_writer)] |
121 | @@ -53,8 +54,8 @@ |
122 | return [] |
123 | tree._content_filter_stack = _content_filter_stack |
124 | self.build_tree_contents([ |
125 | - ('file1.txt', 'Foo Txt'), |
126 | - ('file2.bin', 'Foo Bin')]) |
127 | + (dir + '/file1.txt', 'Foo Txt'), |
128 | + (dir + '/file2.bin', 'Foo Bin')]) |
129 | tree.add(['file1.txt', 'file2.bin']) |
130 | tree.commit('commit raw content') |
131 | txt_fileid = tree.path2id('file1.txt') |
132 | @@ -104,3 +105,57 @@ |
133 | filtered=False).read()) |
134 | self.assertEqual('Foo Bin', tree.get_file(bin_fileid, |
135 | filtered=False).read()) |
136 | + |
137 | + def test_branch_source_filtered_target_not(self): |
138 | + # Create a source branch with content filtering |
139 | + source, txt_fileid, bin_fileid = self.create_cf_tree( |
140 | + txt_reader=_uppercase, txt_writer=_lowercase, dir='source') |
141 | + if not source.supports_content_filtering(): |
142 | + return |
143 | + self.assertFileEqual("Foo Txt", 'source/file1.txt') |
144 | + basis = source.basis_tree() |
145 | + basis.lock_read() |
146 | + self.addCleanup(basis.unlock) |
147 | + self.assertEqual("FOO TXT", basis.get_file_text(txt_fileid)) |
148 | + |
149 | + # Now branch it |
150 | + self.run_bzr('branch source target') |
151 | + target = WorkingTree.open('target') |
152 | + # Even though the content in source and target are different |
153 | + # due to different filters, iter_changes should be clean |
154 | + self.assertFileEqual("FOO TXT", 'target/file1.txt') |
155 | + changes = target.changes_from(source.basis_tree()) |
156 | + self.assertFalse(changes.has_changed()) |
157 | + |
158 | + def test_branch_source_not_filtered_target_is(self): |
159 | + # Create a source branch with content filtering |
160 | + source, txt_fileid, bin_fileid = self.create_cf_tree( |
161 | + txt_reader=None, txt_writer=None, dir='source') |
162 | + if not source.supports_content_filtering(): |
163 | + return |
164 | + self.assertFileEqual("Foo Txt", 'source/file1.txt') |
165 | + basis = source.basis_tree() |
166 | + basis.lock_read() |
167 | + self.addCleanup(basis.unlock) |
168 | + self.assertEqual("Foo Txt", basis.get_file_text(txt_fileid)) |
169 | + |
170 | + # Patch in a custom, symmetric content filter stack |
171 | + self.real_content_filter_stack = WorkingTree._content_filter_stack |
172 | + def restore_real_content_filter_stack(): |
173 | + WorkingTree._content_filter_stack = self.real_content_filter_stack |
174 | + self.addCleanup(restore_real_content_filter_stack) |
175 | + def _content_filter_stack(tree, path=None, file_id=None): |
176 | + if path.endswith('.txt'): |
177 | + return [ContentFilter(_swapcase, _swapcase)] |
178 | + else: |
179 | + return [] |
180 | + WorkingTree._content_filter_stack = _content_filter_stack |
181 | + |
182 | + # Now branch it |
183 | + self.run_bzr('branch source target') |
184 | + target = WorkingTree.open('target') |
185 | + # Even though the content in source and target are different |
186 | + # due to different filters, iter_changes should be clean |
187 | + self.assertFileEqual("fOO tXT", 'target/file1.txt') |
188 | + changes = target.changes_from(source.basis_tree()) |
189 | + self.assertFalse(changes.has_changed()) |
190 | |
191 | === modified file 'bzrlib/tests/workingtree_implementations/test_eol_conversion.py' |
192 | --- bzrlib/tests/workingtree_implementations/test_eol_conversion.py 2009-04-02 04:12:11 +0000 |
193 | +++ bzrlib/tests/workingtree_implementations/test_eol_conversion.py 2009-06-11 04:35:14 +0000 |
194 | @@ -17,8 +17,9 @@ |
195 | """Tests for eol conversion.""" |
196 | |
197 | import sys |
198 | +from cStringIO import StringIO |
199 | |
200 | -from bzrlib import rules |
201 | +from bzrlib import rules, status |
202 | from bzrlib.tests import TestSkipped |
203 | from bzrlib.tests.workingtree_implementations import TestCaseWithWorkingTree |
204 | from bzrlib.workingtree import WorkingTree |
205 | @@ -29,6 +30,13 @@ |
206 | _sample_text_on_win = """hello\r\nworld\r\n""" |
207 | _sample_text_on_unix = """hello\nworld\n""" |
208 | _sample_binary = """hello\nworld\r\n\x00""" |
209 | +_sample_clean_lf = _sample_text_on_unix |
210 | +_sample_clean_crlf = _sample_text_on_win |
211 | + |
212 | + |
213 | +# Lists of formats for each storage policy |
214 | +_LF_IN_REPO = ['native', 'lf', 'crlf'] |
215 | +_CRLF_IN_REPO = [ '%s-with-crlf-in-repo' % (f,) for f in _LF_IN_REPO] |
216 | |
217 | |
218 | class TestEolConversion(TestCaseWithWorkingTree): |
219 | @@ -74,8 +82,11 @@ |
220 | return t, basis |
221 | |
222 | def assertNewContentForSetting(self, wt, eol, expected_unix, |
223 | - expected_win=None): |
224 | - """Clone a working tree and check the convenience content.""" |
225 | + expected_win, roundtrip): |
226 | + """Clone a working tree and check the convenience content. |
227 | + |
228 | + If roundtrip is True, status and commit should see no changes. |
229 | + """ |
230 | if expected_win is None: |
231 | expected_win = expected_unix |
232 | self.patch_rules_searcher(eol) |
233 | @@ -86,100 +97,241 @@ |
234 | self.assertEqual(expected_win, content) |
235 | else: |
236 | self.assertEqual(expected_unix, content) |
237 | + # Confirm that status thinks nothing has changed if the text roundtrips |
238 | + if roundtrip: |
239 | + status_io = StringIO() |
240 | + status.show_tree_status(wt2, to_file=status_io) |
241 | + self.assertEqual('', status_io.getvalue()) |
242 | |
243 | def assertContent(self, wt, basis, expected_raw, expected_unix, |
244 | - expected_win): |
245 | - """Check the committed content and content in cloned trees.""" |
246 | + expected_win, roundtrip_to=None): |
247 | + """Check the committed content and content in cloned trees. |
248 | + |
249 | + :param roundtrip_to: the set of formats (excluding exact) we |
250 | + can round-trip to or None for all |
251 | + """ |
252 | basis_content = basis.get_file('file1-id').read() |
253 | self.assertEqual(expected_raw, basis_content) |
254 | - self.assertNewContentForSetting(wt, None, expected_raw) |
255 | + |
256 | + # No setting and exact should always roundtrip |
257 | + self.assertNewContentForSetting(wt, None, |
258 | + expected_raw, expected_raw, roundtrip=True) |
259 | + self.assertNewContentForSetting(wt, 'exact', |
260 | + expected_raw, expected_raw, roundtrip=True) |
261 | + |
262 | + # Roundtripping is otherwise dependent on whether the original |
263 | + # text is clean - mixed line endings will prevent it. It also |
264 | + # depends on whether the format in the repository is being changed. |
265 | + if roundtrip_to is None: |
266 | + roundtrip_to = _LF_IN_REPO + _CRLF_IN_REPO |
267 | self.assertNewContentForSetting(wt, 'native', |
268 | - expected_unix, expected_win) |
269 | + expected_unix, expected_win, 'native' in roundtrip_to) |
270 | self.assertNewContentForSetting(wt, 'lf', |
271 | - expected_unix, expected_unix) |
272 | + expected_unix, expected_unix, 'lf' in roundtrip_to) |
273 | self.assertNewContentForSetting(wt, 'crlf', |
274 | - expected_win, expected_win) |
275 | + expected_win, expected_win, 'crlf' in roundtrip_to) |
276 | self.assertNewContentForSetting(wt, 'native-with-crlf-in-repo', |
277 | - expected_unix, expected_win) |
278 | + expected_unix, expected_win, |
279 | + 'native-with-crlf-in-repo' in roundtrip_to) |
280 | self.assertNewContentForSetting(wt, 'lf-with-crlf-in-repo', |
281 | - expected_unix, expected_unix) |
282 | + expected_unix, expected_unix, |
283 | + 'lf-with-crlf-in-repo' in roundtrip_to) |
284 | self.assertNewContentForSetting(wt, 'crlf-with-crlf-in-repo', |
285 | - expected_win, expected_win) |
286 | - self.assertNewContentForSetting(wt, 'exact', expected_raw) |
287 | - |
288 | - def test_eol_no_rules(self): |
289 | - wt, basis = self.prepare_tree(_sample_text) |
290 | - self.assertContent(wt, basis, _sample_text, |
291 | - _sample_text_on_unix, _sample_text_on_win) |
292 | - |
293 | - def test_eol_native(self): |
294 | - wt, basis = self.prepare_tree(_sample_text, eol='native') |
295 | - self.assertContent(wt, basis, _sample_text_on_unix, |
296 | - _sample_text_on_unix, _sample_text_on_win) |
297 | + expected_win, expected_win, |
298 | + 'crlf-with-crlf-in-repo' in roundtrip_to) |
299 | + |
300 | + # Test binary files. These always roundtrip. |
301 | + |
302 | + def test_eol_no_rules_binary(self): |
303 | + wt, basis = self.prepare_tree(_sample_binary) |
304 | + self.assertContent(wt, basis, _sample_binary, _sample_binary, |
305 | + _sample_binary) |
306 | + |
307 | + def test_eol_exact_binary(self): |
308 | + wt, basis = self.prepare_tree(_sample_binary, eol='exact') |
309 | + self.assertContent(wt, basis, _sample_binary, _sample_binary, |
310 | + _sample_binary) |
311 | |
312 | def test_eol_native_binary(self): |
313 | wt, basis = self.prepare_tree(_sample_binary, eol='native') |
314 | self.assertContent(wt, basis, _sample_binary, _sample_binary, |
315 | _sample_binary) |
316 | |
317 | - def test_eol_lf(self): |
318 | - wt, basis = self.prepare_tree(_sample_text, eol='lf') |
319 | - self.assertContent(wt, basis, _sample_text_on_unix, |
320 | - _sample_text_on_unix, _sample_text_on_win) |
321 | - |
322 | def test_eol_lf_binary(self): |
323 | wt, basis = self.prepare_tree(_sample_binary, eol='lf') |
324 | self.assertContent(wt, basis, _sample_binary, _sample_binary, |
325 | _sample_binary) |
326 | |
327 | - def test_eol_crlf(self): |
328 | - wt, basis = self.prepare_tree(_sample_text, eol='crlf') |
329 | - self.assertContent(wt, basis, _sample_text_on_unix, |
330 | - _sample_text_on_unix, _sample_text_on_win) |
331 | - |
332 | def test_eol_crlf_binary(self): |
333 | wt, basis = self.prepare_tree(_sample_binary, eol='crlf') |
334 | self.assertContent(wt, basis, _sample_binary, _sample_binary, |
335 | _sample_binary) |
336 | |
337 | - def test_eol_native_with_crlf_in_repo(self): |
338 | - wt, basis = self.prepare_tree(_sample_text, |
339 | - eol='native-with-crlf-in-repo') |
340 | - self.assertContent(wt, basis, _sample_text_on_win, |
341 | - _sample_text_on_unix, _sample_text_on_win) |
342 | - |
343 | def test_eol_native_with_crlf_in_repo_binary(self): |
344 | wt, basis = self.prepare_tree(_sample_binary, |
345 | eol='native-with-crlf-in-repo') |
346 | self.assertContent(wt, basis, _sample_binary, _sample_binary, |
347 | _sample_binary) |
348 | |
349 | - def test_eol_lf_with_crlf_in_repo(self): |
350 | - wt, basis = self.prepare_tree(_sample_text, eol='lf-with-crlf-in-repo') |
351 | - self.assertContent(wt, basis, _sample_text_on_win, |
352 | - _sample_text_on_unix, _sample_text_on_win) |
353 | - |
354 | def test_eol_lf_with_crlf_in_repo_binary(self): |
355 | - wt, basis = self.prepare_tree(_sample_binary, eol='lf-with-crlf-in-repo') |
356 | + wt, basis = self.prepare_tree(_sample_binary, |
357 | + eol='lf-with-crlf-in-repo') |
358 | self.assertContent(wt, basis, _sample_binary, _sample_binary, |
359 | _sample_binary) |
360 | |
361 | - def test_eol_crlf_with_crlf_in_repo(self): |
362 | - wt, basis = self.prepare_tree(_sample_text, eol='crlf-with-crlf-in-repo') |
363 | - self.assertContent(wt, basis, _sample_text_on_win, |
364 | - _sample_text_on_unix, _sample_text_on_win) |
365 | - |
366 | def test_eol_crlf_with_crlf_in_repo_binary(self): |
367 | - wt, basis = self.prepare_tree(_sample_binary, eol='crlf-with-crlf-in-repo') |
368 | + wt, basis = self.prepare_tree(_sample_binary, |
369 | + eol='crlf-with-crlf-in-repo') |
370 | self.assertContent(wt, basis, _sample_binary, _sample_binary, |
371 | _sample_binary) |
372 | |
373 | - def test_eol_exact(self): |
374 | + # Test text with mixed line endings ("dirty text"). |
375 | + # This doesn't roundtrip so status always thinks something has changed. |
376 | + |
377 | + def test_eol_no_rules_dirty(self): |
378 | + wt, basis = self.prepare_tree(_sample_text) |
379 | + self.assertContent(wt, basis, _sample_text, |
380 | + _sample_text_on_unix, _sample_text_on_win, roundtrip_to=[]) |
381 | + |
382 | + def test_eol_exact_dirty(self): |
383 | wt, basis = self.prepare_tree(_sample_text, eol='exact') |
384 | self.assertContent(wt, basis, _sample_text, |
385 | - _sample_text_on_unix, _sample_text_on_win) |
386 | - |
387 | - def test_eol_exact_binary(self): |
388 | - wt, basis = self.prepare_tree(_sample_binary, eol='exact') |
389 | - self.assertContent(wt, basis, _sample_binary, _sample_binary, |
390 | - _sample_binary) |
391 | + _sample_text_on_unix, _sample_text_on_win, roundtrip_to=[]) |
392 | + |
393 | + def test_eol_native_dirty(self): |
394 | + wt, basis = self.prepare_tree(_sample_text, eol='native') |
395 | + self.assertContent(wt, basis, _sample_text_on_unix, |
396 | + _sample_text_on_unix, _sample_text_on_win, roundtrip_to=[]) |
397 | + |
398 | + def test_eol_lf_dirty(self): |
399 | + wt, basis = self.prepare_tree(_sample_text, eol='lf') |
400 | + self.assertContent(wt, basis, _sample_text_on_unix, |
401 | + _sample_text_on_unix, _sample_text_on_win, roundtrip_to=[]) |
402 | + |
403 | + def test_eol_crlf_dirty(self): |
404 | + wt, basis = self.prepare_tree(_sample_text, eol='crlf') |
405 | + self.assertContent(wt, basis, _sample_text_on_unix, |
406 | + _sample_text_on_unix, _sample_text_on_win, roundtrip_to=[]) |
407 | + |
408 | + def test_eol_native_with_crlf_in_repo_dirty(self): |
409 | + wt, basis = self.prepare_tree(_sample_text, |
410 | + eol='native-with-crlf-in-repo') |
411 | + self.assertContent(wt, basis, _sample_text_on_win, |
412 | + _sample_text_on_unix, _sample_text_on_win, roundtrip_to=[]) |
413 | + |
414 | + def test_eol_lf_with_crlf_in_repo_dirty(self): |
415 | + wt, basis = self.prepare_tree(_sample_text, |
416 | + eol='lf-with-crlf-in-repo') |
417 | + self.assertContent(wt, basis, _sample_text_on_win, |
418 | + _sample_text_on_unix, _sample_text_on_win, roundtrip_to=[]) |
419 | + |
420 | + def test_eol_crlf_with_crlf_in_repo_dirty(self): |
421 | + wt, basis = self.prepare_tree(_sample_text, |
422 | + eol='crlf-with-crlf-in-repo') |
423 | + self.assertContent(wt, basis, _sample_text_on_win, |
424 | + _sample_text_on_unix, _sample_text_on_win, roundtrip_to=[]) |
425 | + |
426 | + # Test text with clean line endings, either always lf or always crlf. |
427 | + # This selectively roundtrips (based on what's stored in the repo). |
428 | + |
429 | + def test_eol_no_rules_clean_lf(self): |
430 | + wt, basis = self.prepare_tree(_sample_clean_lf) |
431 | + self.assertContent(wt, basis, _sample_clean_lf, |
432 | + _sample_text_on_unix, _sample_text_on_win, |
433 | + roundtrip_to=_LF_IN_REPO) |
434 | + |
435 | + def test_eol_no_rules_clean_crlf(self): |
436 | + wt, basis = self.prepare_tree(_sample_clean_crlf) |
437 | + self.assertContent(wt, basis, _sample_clean_crlf, |
438 | + _sample_text_on_unix, _sample_text_on_win, |
439 | + roundtrip_to=_CRLF_IN_REPO) |
440 | + |
441 | + def test_eol_exact_clean_lf(self): |
442 | + wt, basis = self.prepare_tree(_sample_clean_lf, eol='exact') |
443 | + self.assertContent(wt, basis, _sample_clean_lf, |
444 | + _sample_text_on_unix, _sample_text_on_win, |
445 | + roundtrip_to=_LF_IN_REPO) |
446 | + |
447 | + def test_eol_exact_clean_crlf(self): |
448 | + wt, basis = self.prepare_tree(_sample_clean_crlf, eol='exact') |
449 | + self.assertContent(wt, basis, _sample_clean_crlf, |
450 | + _sample_text_on_unix, _sample_text_on_win, |
451 | + roundtrip_to=_CRLF_IN_REPO) |
452 | + |
453 | + def test_eol_native_clean_lf(self): |
454 | + wt, basis = self.prepare_tree(_sample_clean_lf, eol='native') |
455 | + self.assertContent(wt, basis, _sample_text_on_unix, |
456 | + _sample_text_on_unix, _sample_text_on_win, |
457 | + roundtrip_to=_LF_IN_REPO) |
458 | + |
459 | + def test_eol_native_clean_crlf(self): |
460 | + wt, basis = self.prepare_tree(_sample_clean_crlf, eol='native') |
461 | + self.assertContent(wt, basis, _sample_text_on_unix, |
462 | + _sample_text_on_unix, _sample_text_on_win, |
463 | + roundtrip_to=_LF_IN_REPO) |
464 | + |
465 | + def test_eol_lf_clean_lf(self): |
466 | + wt, basis = self.prepare_tree(_sample_clean_lf, eol='lf') |
467 | + self.assertContent(wt, basis, _sample_text_on_unix, |
468 | + _sample_text_on_unix, _sample_text_on_win, |
469 | + roundtrip_to=_LF_IN_REPO) |
470 | + |
471 | + def test_eol_lf_clean_crlf(self): |
472 | + wt, basis = self.prepare_tree(_sample_clean_crlf, eol='lf') |
473 | + self.assertContent(wt, basis, _sample_text_on_unix, |
474 | + _sample_text_on_unix, _sample_text_on_win, |
475 | + roundtrip_to=_LF_IN_REPO) |
476 | + |
477 | + def test_eol_crlf_clean_lf(self): |
478 | + wt, basis = self.prepare_tree(_sample_clean_lf, eol='crlf') |
479 | + self.assertContent(wt, basis, _sample_text_on_unix, |
480 | + _sample_text_on_unix, _sample_text_on_win, |
481 | + roundtrip_to=_LF_IN_REPO) |
482 | + |
483 | + def test_eol_crlf_clean_crlf(self): |
484 | + wt, basis = self.prepare_tree(_sample_clean_crlf, eol='crlf') |
485 | + self.assertContent(wt, basis, _sample_text_on_unix, |
486 | + _sample_text_on_unix, _sample_text_on_win, |
487 | + roundtrip_to=_LF_IN_REPO) |
488 | + |
489 | + def test_eol_native_with_crlf_in_repo_clean_lf(self): |
490 | + wt, basis = self.prepare_tree(_sample_clean_lf, |
491 | + eol='native-with-crlf-in-repo') |
492 | + self.assertContent(wt, basis, _sample_text_on_win, |
493 | + _sample_text_on_unix, _sample_text_on_win, |
494 | + roundtrip_to=_CRLF_IN_REPO) |
495 | + |
496 | + def test_eol_native_with_crlf_in_repo_clean_crlf(self): |
497 | + wt, basis = self.prepare_tree(_sample_clean_crlf, |
498 | + eol='native-with-crlf-in-repo') |
499 | + self.assertContent(wt, basis, _sample_text_on_win, |
500 | + _sample_text_on_unix, _sample_text_on_win, |
501 | + roundtrip_to=_CRLF_IN_REPO) |
502 | + |
503 | + def test_eol_lf_with_crlf_in_repo_clean_lf(self): |
504 | + wt, basis = self.prepare_tree(_sample_clean_lf, |
505 | + eol='lf-with-crlf-in-repo') |
506 | + self.assertContent(wt, basis, _sample_text_on_win, |
507 | + _sample_text_on_unix, _sample_text_on_win, |
508 | + roundtrip_to=_CRLF_IN_REPO) |
509 | + |
510 | + def test_eol_lf_with_crlf_in_repo_clean_crlf(self): |
511 | + wt, basis = self.prepare_tree(_sample_clean_crlf, |
512 | + eol='lf-with-crlf-in-repo') |
513 | + self.assertContent(wt, basis, _sample_text_on_win, |
514 | + _sample_text_on_unix, _sample_text_on_win, |
515 | + roundtrip_to=_CRLF_IN_REPO) |
516 | + |
517 | + def test_eol_crlf_with_crlf_in_repo_clean_lf(self): |
518 | + wt, basis = self.prepare_tree(_sample_clean_lf, |
519 | + eol='crlf-with-crlf-in-repo') |
520 | + self.assertContent(wt, basis, _sample_text_on_win, |
521 | + _sample_text_on_unix, _sample_text_on_win, |
522 | + roundtrip_to=_CRLF_IN_REPO) |
523 | + |
524 | + def test_eol_crlf_with_crlf_in_repo_clean_crlf(self): |
525 | + wt, basis = self.prepare_tree(_sample_clean_crlf, |
526 | + eol='crlf-with-crlf-in-repo') |
527 | + self.assertContent(wt, basis, _sample_text_on_win, |
528 | + _sample_text_on_unix, _sample_text_on_win, |
529 | + roundtrip_to=_CRLF_IN_REPO) |
530 | |
531 | === modified file 'bzrlib/workingtree_4.py' |
532 | --- bzrlib/workingtree_4.py 2009-06-10 03:56:49 +0000 |
533 | +++ bzrlib/workingtree_4.py 2009-06-11 04:35:14 +0000 |
534 | @@ -1440,13 +1440,20 @@ |
535 | # Note: do NOT move this logic up higher - using the basis from |
536 | # the accelerator tree is still desirable because that can save |
537 | # a minute or more of processing on large trees! |
538 | + # The original tree may not have the same content filters |
539 | + # applied so we can't safely build the inventory delta from |
540 | + # the source tree. |
541 | if wt.supports_content_filtering(): |
542 | accelerator_tree = None |
543 | + delta_from_tree = False |
544 | + else: |
545 | + delta_from_tree = True |
546 | # delta_from_tree is safe even for DirStateRevisionTrees, |
547 | # because wt4.apply_inventory_delta does not mutate the input |
548 | # inventory entries. |
549 | transform.build_tree(basis, wt, accelerator_tree, |
550 | - hardlink=hardlink, delta_from_tree=True) |
551 | + hardlink=hardlink, |
552 | + delta_from_tree=delta_from_tree) |
553 | finally: |
554 | basis.unlock() |
555 | finally: |
This patch fixes some very bad bugs in content filtering. There are actually 2 separate issues fixed:
1. Branching from a non-content- filtered tree to a content-filtered one would produce an incorrect dirstate because the optimisation to reuse the source dirstate wasn't being disabled
2. Status detection was relying on size matching to determine equivalence when that assumption doesn't hold in the presence of content filtering.
Both code changes are quite straightforward, though the latter one requires fixes in both Python and Pyrex code. Testing all of this is far more complex though. I've added a heap more units tests and I'm comfortable that status on content filtered trees now has pretty good coverage.
I'm yet to add detailed tests for the branch/commit issue, because the monkey-patching approach used to test status isn't at the right level to work for branch. I have tested it manually though and one of the bug reporters (Frits) has confirmed that the code fixes works correctly for him. Given the related code change - switching off an optional optimisation - is very straight forward, I think this patch can land as is for 1.16rc1.
FWIW, my plan is to add those additional tests soon. (Some pending changes to the monkey-patching used here to fix some other bugs will make that easier, but they aren't ready to go, and this really needs to be landed for content filtering to be unbroken for users.)