Merge lp:~ian-clatworthy/bzr/eol-st-ci-fix into lp:~bzr/bzr/trunk-old

Proposed by Ian Clatworthy
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
Reviewer Review Type Date Requested Status
John A Meinel Approve
Review via email: mp+7269@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote :

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.)

Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (4.9 KiB)

-----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-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.)
>

+ # 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-into-repository code to record
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_content_filtering():
                     accelerator_tree = None
+ delta_from_tree = False
+ else:
+ delta...

Read more...

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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: