Merge lp:~ian-clatworthy/bzr/eol-update-bug into lp:bzr/2.0

Proposed by Ian Clatworthy
Status: Merged
Merged at revision: not available
Proposed branch: lp:~ian-clatworthy/bzr/eol-update-bug
Merge into: lp:bzr/2.0
Diff against target: 325 lines
To merge this branch: bzr merge lp:~ian-clatworthy/bzr/eol-update-bug
Reviewer Review Type Date Requested Status
Robert Collins (community) Needs Fixing
Review via email: mp+10959@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote :

This patch fixes content filtering so that it works after most operations, not just after the initial branch/checkout. There were 2 code paths in transform.py that needed enhancing to check for content filters: one used by 'merging' and one used by 'reverting'. The merging code path is used by lots of operations including merge, pull, update and switch.

I've added several 'blackbox-style' tests as part of this patch. It probably wouldn't hurt to add some more for other operations, e.g. shelve/unshelve, though I'm optimistic that the 2 code paths above will cover 99% of them.

In any case, I'm submitting this for review now because:

1. Without these code changes, content filtering is effectively broken for most users.

2. The 2.0 code freeze is really close and I want to maximise the time available for review of what's fixed/tested already.

3. Those tests could come in a latter patch (after there's agreement on whether blackbox-style tests are acceptable here or not).

BTW, the bug report also mentions that after committing a tree with keywords expanded, the keywords aren't re-expanded after commit. I'm going to work on that issue now, either by adding a post-commit hook to the keywords plugin or by changing commit. The latter approach implies always paying a performance cost whether it's required or not so I'll try the post-commit approach first. Either way, I feel it's best to keep that issue out of scope of this patch.

Revision history for this message
Robert Collins (lifeless) wrote :

>> BTW, the bug report also mentions that after committing a tree with keywords expanded, the keywords aren't re-expanded after commit.

Commit does not write to the users tree : a post commit hook isn't appropriate [for starters, other post commit hooks could interfere/see convenience forms themselves etc]. I suggest looking closely for the cause/some possible confusion is going on here.

Revision history for this message
Robert Collins (lifeless) wrote :

I'm trying to channel Aaron here, and I don't know the transform code *all that well*. However, what you've done looks wrong to me.

Specifically, you look up a path in one tree, and use that to select filters in a different tree (this is at the very top of your patch).

This has the problem that when a containing directory is renamed in this tree, it will be matching on a different path than actually will be used.

Secondly, as filters are defined as being path based, *every* change to a transform that can cause a change to the path of something that can be filtered needs to recalculate the filter stack and possibly reapply it, for all those somethings. E.g. renaming a directory foo/baz to bar/baz, and foo was filtered and bar isn't.

I think the change to the API to pass in a path that is able to be wrong reduces clarity and shouldn't be done. We can probably get by (with a critical bug to be fixed asap after the release) with out updating filters appropriately.

review: Needs Fixing
Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote :

Robert Collins wrote:
> Review: Needs Fixing

Thanks for the feedback. poolie is taking over this patch from here so I
can focus on the Windows installer. I'll let him look deeper but some
quick comments on your first point ...

> Specifically, you look up a path in one tree, and use that to select filters in a different tree (this is at the very top of your patch).

The patch mightn't be quite right. I can say however that this cross
tree stuff is tricky. Keep in mind that:

* in the case of reverting a removed file, its path doesn't exist
  in the working tree so the only place to find that is the rev-tree

* rules aren't stored historically so, IIRC, revtrees don't know
  what they are - only the WT knows that.

So looking up a path in the revtree and using the filters from the WT
may indeed be correct, in the case of revert at least.

Merging has similar challenges. A new path may only exist in the OTHER
tree say but the filters applied need to come from the working tree
being merged into.

Ian C.

Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote :

Robert Collins wrote:
>>> BTW, the bug report also mentions that after committing a tree with keywords expanded, the keywords aren't re-expanded after commit.
>
> Commit does not write to the users tree : a post commit hook isn't appropriate [for starters, other post commit hooks could interfere/see convenience forms themselves etc]. I suggest looking closely for the cause/some possible confusion is going on here.

Plugins like keywords do need to update the working tree, if any, after
a commit so that files just committed get keywords values updated. As
the keywords plugin is currently designed, the only files needing
potential update are the newly committed ones.

I agree a post commit hook isn't the answer. I've put a separate patch
up for a finish-commit hook on mutable trees instead.

Ian C.

Revision history for this message
Robert Collins (lifeless) wrote :

On Thu, 2009-09-03 at 00:57 +0000, Ian Clatworthy wrote:
>
> > Commit does not write to the users tree : a post commit hook isn't
> appropriate [for starters, other post commit hooks could interfere/see
> convenience forms themselves etc]. I suggest looking closely for the
> cause/some possible confusion is going on here.
>
> Plugins like keywords do need to update the working tree, if any,
> after
> a commit so that files just committed get keywords values updated. As
> the keywords plugin is currently designed, the only files needing
> potential update are the newly committed ones.
>
> I agree a post commit hook isn't the answer. I've put a separate patch
> up for a finish-commit hook on mutable trees instead.

Oh, that use case wasn't clear - I didn't get what was needed.

Branch.post_commit definitely isn't the right thing as its on branch.

There may be room for MutableTree.post_commit too - the difference being
whether or not <activity> should occur before the basis revision id is
changed.

So, the question to ask is 'should keywords update the files *after* the
basis changes, or *before*. I think the answer is *after*, but you've
spent more time staring at this problem: its up to you.

I do suggest that the hook name try to reflect where in the process its
happening, and if its after commit has done its stuff, its really just a
post commit hook on a different object, so lets use the namespaces and
call it MT.post_commit

-Rob

Revision history for this message
Robert Collins (lifeless) wrote :

On Thu, 2009-09-03 at 00:54 +0000, Ian Clatworthy wrote:
>
> The patch mightn't be quite right. I can say however that this cross
> tree stuff is tricky. Keep in mind that:
>
> * in the case of reverting a removed file, its path doesn't exist
> in the working tree so the only place to find that is the rev-tree

It has a path that it will end up with in the output tree; thats the one
that should be used, right? Paths in other trees are a poor proxy for
that.

> So looking up a path in the revtree and using the filters from the WT
> may indeed be correct, in the case of revert at least.

I argue that it would only be accurate in the most trivial of cases;
solving the primary problem - that a transform may need to reapply
filters some N times - will solve the other cases as well.

> Merging has similar challenges. A new path may only exist in the OTHER
> tree say but the filters applied need to come from the working tree
> being merged into.

Right, its exactly what I'm driving at.

-Rob

Revision history for this message
Martin Pool (mbp) wrote :

I don't think this is safe for 2.0 but it might be ok for 2.1b1

280 +def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
281 + filter_tree_path=None):
282 + """Create new file contents according to tree contents.
283 +
284 + :param filter_tree_path: the tree path to use to lookup
285 + content filters to apply to the bytes output in the working tree.
286 + This only applies if the working tree supports content filtering.
287 + """

I don't understand how that new parameter works:

Is it a single file? How can that work with tt working across a whole tree?

Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote :

> I don't think this is safe for 2.0 but it might be ok for 2.1b1
>
> 280 +def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
> 281 + filter_tree_path=None):
> 282 + """Create new file contents according to tree contents.
> 283 +
> 284 + :param filter_tree_path: the tree path to use to lookup
> 285 + content filters to apply to the bytes output in the working tree.
> 286 + This only applies if the working tree supports content filtering.
> 287 + """
>
> I don't understand how that new parameter works:
>
> Is it a single file? How can that work with tt working across a whole tree?

This API is called per file-id, it's not called per tree.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-08-31 00:25:33 +0000
+++ NEWS 2009-09-01 06:35:12 +0000
@@ -20,6 +20,9 @@
20 revisions that are in the fallback repository. (Regressed in 2.0rc1).20 revisions that are in the fallback repository. (Regressed in 2.0rc1).
21 (John Arbash Meinel, #419241)21 (John Arbash Meinel, #419241)
2222
23* Content filters are now applied correctly after pull, merge, switch
24 and revert. (Ian Clatworthy, #385879)
25
23* Fix a segmentation fault when computing the ``merge_sort`` of a graph26* Fix a segmentation fault when computing the ``merge_sort`` of a graph
24 that has a ghost in the mainline ancestry.27 that has a ghost in the mainline ancestry.
25 (John Arbash Meinel, #419241)28 (John Arbash Meinel, #419241)
2629
=== modified file 'bzrlib/merge.py'
--- bzrlib/merge.py 2009-08-26 03:20:32 +0000
+++ bzrlib/merge.py 2009-09-01 06:35:12 +0000
@@ -1160,8 +1160,15 @@
1160 self.tt.delete_contents(trans_id)1160 self.tt.delete_contents(trans_id)
1161 if file_id in self.other_tree:1161 if file_id in self.other_tree:
1162 # OTHER changed the file1162 # OTHER changed the file
1163 wt = self.this_tree
1164 if wt.supports_content_filtering():
1165 filter_tree_path = self.other_tree.id2path(file_id)
1166 else:
1167 # Skip the id2path lookup for older formats
1168 filter_tree_path = None
1163 create_from_tree(self.tt, trans_id,1169 create_from_tree(self.tt, trans_id,
1164 self.other_tree, file_id)1170 self.other_tree, file_id,
1171 filter_tree_path=filter_tree_path)
1165 if not file_in_this:1172 if not file_in_this:
1166 self.tt.version_file(file_id, trans_id)1173 self.tt.version_file(file_id, trans_id)
1167 return "modified"1174 return "modified"
@@ -1254,12 +1261,26 @@
1254 ('THIS', self.this_tree, this_lines)]1261 ('THIS', self.this_tree, this_lines)]
1255 if not no_base:1262 if not no_base:
1256 data.append(('BASE', self.base_tree, base_lines))1263 data.append(('BASE', self.base_tree, base_lines))
1264
1265 # We need to use the actual path in the working tree of the file here,
1266 # ignoring the conflict suffixes
1267 wt = self.this_tree
1268 if wt.supports_content_filtering():
1269 try:
1270 filter_tree_path = wt.id2path(file_id)
1271 except errors.NoSuchId:
1272 # file has been deleted
1273 filter_tree_path = None
1274 else:
1275 # Skip the id2path lookup for older formats
1276 filter_tree_path = None
1277
1257 versioned = False1278 versioned = False
1258 file_group = []1279 file_group = []
1259 for suffix, tree, lines in data:1280 for suffix, tree, lines in data:
1260 if file_id in tree:1281 if file_id in tree:
1261 trans_id = self._conflict_file(name, parent_id, tree, file_id,1282 trans_id = self._conflict_file(name, parent_id, tree, file_id,
1262 suffix, lines)1283 suffix, lines, filter_tree_path)
1263 file_group.append(trans_id)1284 file_group.append(trans_id)
1264 if set_version and not versioned:1285 if set_version and not versioned:
1265 self.tt.version_file(file_id, trans_id)1286 self.tt.version_file(file_id, trans_id)
@@ -1267,11 +1288,12 @@
1267 return file_group1288 return file_group
12681289
1269 def _conflict_file(self, name, parent_id, tree, file_id, suffix,1290 def _conflict_file(self, name, parent_id, tree, file_id, suffix,
1270 lines=None):1291 lines=None, filter_tree_path=None):
1271 """Emit a single conflict file."""1292 """Emit a single conflict file."""
1272 name = name + '.' + suffix1293 name = name + '.' + suffix
1273 trans_id = self.tt.create_path(name, parent_id)1294 trans_id = self.tt.create_path(name, parent_id)
1274 create_from_tree(self.tt, trans_id, tree, file_id, lines)1295 create_from_tree(self.tt, trans_id, tree, file_id, lines,
1296 filter_tree_path)
1275 return trans_id1297 return trans_id
12761298
1277 def merge_executable(self, file_id, file_status):1299 def merge_executable(self, file_id, file_status):
12781300
=== modified file 'bzrlib/tests/per_workingtree/test_content_filters.py'
--- bzrlib/tests/per_workingtree/test_content_filters.py 2009-08-25 04:43:21 +0000
+++ bzrlib/tests/per_workingtree/test_content_filters.py 2009-09-01 06:35:12 +0000
@@ -16,7 +16,11 @@
1616
17"""Tests for content filtering conformance"""17"""Tests for content filtering conformance"""
1818
19import os
20
21from bzrlib.bzrdir import BzrDir
19from bzrlib.filters import ContentFilter22from bzrlib.filters import ContentFilter
23from bzrlib.switch import switch
20from bzrlib.workingtree import WorkingTree24from bzrlib.workingtree import WorkingTree
21from bzrlib.tests.per_workingtree import TestCaseWithWorkingTree25from bzrlib.tests.per_workingtree import TestCaseWithWorkingTree
2226
@@ -64,7 +68,8 @@
6468
65class TestWorkingTreeWithContentFilters(TestCaseWithWorkingTree):69class TestWorkingTreeWithContentFilters(TestCaseWithWorkingTree):
6670
67 def create_cf_tree(self, txt_reader, txt_writer, dir='.'):71 def create_cf_tree(self, txt_reader, txt_writer, dir='.',
72 two_revisions=False):
68 tree = self.make_branch_and_tree(dir)73 tree = self.make_branch_and_tree(dir)
69 def _content_filter_stack(path=None, file_id=None):74 def _content_filter_stack(path=None, file_id=None):
70 if path.endswith('.txt'):75 if path.endswith('.txt'):
@@ -77,10 +82,37 @@
77 (dir + '/file2.bin', 'Foo Bin')])82 (dir + '/file2.bin', 'Foo Bin')])
78 tree.add(['file1.txt', 'file2.bin'])83 tree.add(['file1.txt', 'file2.bin'])
79 tree.commit('commit raw content')84 tree.commit('commit raw content')
85 # Commit another revision with changed text, if requested
86 if two_revisions:
87 self.build_tree_contents([(dir + '/file1.txt', 'Foo ROCKS!')])
88 tree.commit("changed file1.txt")
80 txt_fileid = tree.path2id('file1.txt')89 txt_fileid = tree.path2id('file1.txt')
81 bin_fileid = tree.path2id('file2.bin')90 bin_fileid = tree.path2id('file2.bin')
82 return tree, txt_fileid, bin_fileid91 return tree, txt_fileid, bin_fileid
8392
93 def patch_in_content_filter(self):
94 # Patch in a custom, symmetric content filter stack
95 self.real_content_filter_stack = WorkingTree._content_filter_stack
96 def restore_real_content_filter_stack():
97 WorkingTree._content_filter_stack = self.real_content_filter_stack
98 self.addCleanup(restore_real_content_filter_stack)
99 def _content_filter_stack(tree, path=None, file_id=None):
100 if path.endswith('.txt'):
101 return [ContentFilter(_swapcase, _swapcase)]
102 else:
103 return []
104 WorkingTree._content_filter_stack = _content_filter_stack
105
106 def assert_basis_content(self, expected_content, branch, file_id):
107 # Note: We need to use try/finally here instead of addCleanup()
108 # as the latter leaves the read lock in place too long
109 basis = branch.basis_tree()
110 basis.lock_read()
111 try:
112 self.assertEqual(expected_content, basis.get_file_text(file_id))
113 finally:
114 basis.unlock()
115
84 def test_symmetric_content_filtering(self):116 def test_symmetric_content_filtering(self):
85 # test handling when read then write gives back the initial content117 # test handling when read then write gives back the initial content
86 tree, txt_fileid, bin_fileid = self.create_cf_tree(118 tree, txt_fileid, bin_fileid = self.create_cf_tree(
@@ -132,10 +164,7 @@
132 if not source.supports_content_filtering():164 if not source.supports_content_filtering():
133 return165 return
134 self.assertFileEqual("Foo Txt", 'source/file1.txt')166 self.assertFileEqual("Foo Txt", 'source/file1.txt')
135 basis = source.basis_tree()167 self.assert_basis_content("FOO TXT", source, txt_fileid)
136 basis.lock_read()
137 self.addCleanup(basis.unlock)
138 self.assertEqual("FOO TXT", basis.get_file_text(txt_fileid))
139168
140 # Now branch it169 # Now branch it
141 self.run_bzr('branch source target')170 self.run_bzr('branch source target')
@@ -153,24 +182,10 @@
153 if not source.supports_content_filtering():182 if not source.supports_content_filtering():
154 return183 return
155 self.assertFileEqual("Foo Txt", 'source/file1.txt')184 self.assertFileEqual("Foo Txt", 'source/file1.txt')
156 basis = source.basis_tree()185 self.assert_basis_content("Foo Txt", source, txt_fileid)
157 basis.lock_read()186
158 self.addCleanup(basis.unlock)187 # Now patch in content filtering and branch the source
159 self.assertEqual("Foo Txt", basis.get_file_text(txt_fileid))188 self.patch_in_content_filter()
160
161 # Patch in a custom, symmetric content filter stack
162 self.real_content_filter_stack = WorkingTree._content_filter_stack
163 def restore_real_content_filter_stack():
164 WorkingTree._content_filter_stack = self.real_content_filter_stack
165 self.addCleanup(restore_real_content_filter_stack)
166 def _content_filter_stack(tree, path=None, file_id=None):
167 if path.endswith('.txt'):
168 return [ContentFilter(_swapcase, _swapcase)]
169 else:
170 return []
171 WorkingTree._content_filter_stack = _content_filter_stack
172
173 # Now branch it
174 self.run_bzr('branch source target')189 self.run_bzr('branch source target')
175 target = WorkingTree.open('target')190 target = WorkingTree.open('target')
176 # Even though the content in source and target are different191 # Even though the content in source and target are different
@@ -209,3 +224,86 @@
209 # we could give back the length of the canonical form, but in general224 # we could give back the length of the canonical form, but in general
210 # that will be expensive to compute, so it's acceptable to just return225 # that will be expensive to compute, so it's acceptable to just return
211 # None.226 # None.
227
228 def test_content_filtering_applied_on_pull(self):
229 # Create a source branch with two revisions
230 source, txt_fileid, bin_fileid = self.create_cf_tree(
231 txt_reader=None, txt_writer=None, dir='source', two_revisions=True)
232 if not source.supports_content_filtering():
233 return
234 self.assertFileEqual("Foo ROCKS!", 'source/file1.txt')
235 self.assert_basis_content("Foo ROCKS!", source, txt_fileid)
236
237 # Now patch in content filtering and branch from revision 1
238 self.patch_in_content_filter()
239 self.run_bzr('branch -r1 source target')
240 target = WorkingTree.open('target')
241 self.assertFileEqual("fOO tXT", 'target/file1.txt')
242 self.assert_basis_content("Foo Txt", target, txt_fileid)
243
244 # Pull the latter change and check the target tree is updated
245 self.run_bzr('pull -d target')
246 self.assertFileEqual("fOO rocks!", 'target/file1.txt')
247 self.assert_basis_content("Foo ROCKS!", target, txt_fileid)
248
249 def test_content_filtering_applied_on_merge(self):
250 # Create a source branch with two revisions
251 source, txt_fileid, bin_fileid = self.create_cf_tree(
252 txt_reader=None, txt_writer=None, dir='source', two_revisions=True)
253 if not source.supports_content_filtering():
254 return
255 self.assertFileEqual("Foo ROCKS!", 'source/file1.txt')
256 self.assert_basis_content("Foo ROCKS!", source, txt_fileid)
257
258 # Now patch in content filtering and branch from revision 1
259 self.patch_in_content_filter()
260 self.run_bzr('branch -r1 source target')
261 target = WorkingTree.open('target')
262 self.assertFileEqual("fOO tXT", 'target/file1.txt')
263 self.assert_basis_content("Foo Txt", target, txt_fileid)
264
265 # Merge the latter change and check the target tree is updated
266 self.run_bzr('merge -d target source')
267 self.assertFileEqual("fOO rocks!", 'target/file1.txt')
268
269 # Commit the merge and check the right content is stored
270 target.commit("merge file1.txt changes from source")
271 self.assert_basis_content("Foo ROCKS!", target, txt_fileid)
272
273 def test_content_filtering_applied_on_switch(self):
274 # Create a source branch with two revisions
275 source, txt_fileid, bin_fileid = self.create_cf_tree(
276 txt_reader=None, txt_writer=None, dir='branch-a', two_revisions=True)
277 if not source.supports_content_filtering():
278 return
279
280 # Now patch in content filtering and branch from revision 1
281 self.patch_in_content_filter()
282 self.run_bzr('branch -r1 branch-a branch-b')
283
284 # Now create a lightweight checkout referring to branch-b
285 self.run_bzr('checkout --lightweight branch-b checkout')
286 self.assertFileEqual("fOO tXT", 'checkout/file1.txt')
287
288 # Switch it to branch-b and check the tree is updated
289 checkout_control_dir = BzrDir.open_containing('checkout')[0]
290 switch(checkout_control_dir, source.branch)
291 self.assertFileEqual("fOO rocks!", 'checkout/file1.txt')
292
293 def test_content_filtering_applied_on_revert(self):
294 # Create a source branch with content filtering
295 source, txt_fileid, bin_fileid = self.create_cf_tree(
296 txt_reader=_uppercase, txt_writer=_lowercase, dir='source')
297 if not source.supports_content_filtering():
298 return
299 self.assertFileEqual("Foo Txt", 'source/file1.txt')
300 self.assert_basis_content("FOO TXT", source, txt_fileid)
301
302 # Now delete the file, revert it and check the content
303 os.unlink('source/file1.txt')
304 self.assertFalse(os.path.exists('source/file1.txt'))
305 source.revert(['file1.txt'])
306 self.assertTrue(os.path.exists('source/file1.txt'))
307 # Note: we don't get back exactly what was in the tree
308 # previously because lower(upper(text)) is a lossy transformation
309 self.assertFileEqual("foo txt", 'source/file1.txt')
212310
=== modified file 'bzrlib/transform.py'
--- bzrlib/transform.py 2009-08-26 05:38:16 +0000
+++ bzrlib/transform.py 2009-09-01 06:35:12 +0000
@@ -2402,8 +2402,14 @@
2402 tt.create_directory(trans_id)2402 tt.create_directory(trans_id)
24032403
24042404
2405def create_from_tree(tt, trans_id, tree, file_id, bytes=None):2405def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2406 """Create new file contents according to tree contents."""2406 filter_tree_path=None):
2407 """Create new file contents according to tree contents.
2408
2409 :param filter_tree_path: the tree path to use to lookup
2410 content filters to apply to the bytes output in the working tree.
2411 This only applies if the working tree supports content filtering.
2412 """
2407 kind = tree.kind(file_id)2413 kind = tree.kind(file_id)
2408 if kind == 'directory':2414 if kind == 'directory':
2409 tt.create_directory(trans_id)2415 tt.create_directory(trans_id)
@@ -2414,6 +2420,11 @@
2414 bytes = tree_file.readlines()2420 bytes = tree_file.readlines()
2415 finally:2421 finally:
2416 tree_file.close()2422 tree_file.close()
2423 wt = tt._tree
2424 if wt.supports_content_filtering() and filter_tree_path is not None:
2425 filters = wt._content_filter_stack(filter_tree_path)
2426 bytes = filtered_output_bytes(bytes, filters,
2427 ContentFilterContext(filter_tree_path, tree))
2417 tt.create_file(bytes, trans_id)2428 tt.create_file(bytes, trans_id)
2418 elif kind == "symlink":2429 elif kind == "symlink":
2419 tt.create_symlink(tree.get_symlink_target(file_id), trans_id)2430 tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
@@ -2610,9 +2621,19 @@
2610 tt.adjust_path(name[1], parent_trans, trans_id)2621 tt.adjust_path(name[1], parent_trans, trans_id)
2611 if executable[0] != executable[1] and kind[1] == "file":2622 if executable[0] != executable[1] and kind[1] == "file":
2612 tt.set_executability(executable[1], trans_id)2623 tt.set_executability(executable[1], trans_id)
2613 for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(2624 if working_tree.supports_content_filtering():
2614 deferred_files):2625 for index, ((trans_id, mode_id), bytes) in enumerate(
2615 tt.create_file(bytes, trans_id, mode_id)2626 target_tree.iter_files_bytes(deferred_files)):
2627 file_id = deferred_files[index][0]
2628 filter_tree_path = target_tree.id2path(file_id)
2629 filters = working_tree._content_filter_stack(filter_tree_path)
2630 bytes = filtered_output_bytes(bytes, filters,
2631 ContentFilterContext(filter_tree_path, working_tree))
2632 tt.create_file(bytes, trans_id, mode_id)
2633 else:
2634 for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2635 deferred_files):
2636 tt.create_file(bytes, trans_id, mode_id)
2616 finally:2637 finally:
2617 if basis_tree is not None:2638 if basis_tree is not None:
2618 basis_tree.unlock()2639 basis_tree.unlock()

Subscribers

People subscribed via source and target branches