Merge lp:~jameinel/bzr/2.4-dirstate-invalid-delta-855155 into lp:bzr/2.4

Proposed by John A Meinel
Status: Merged
Approved by: John A Meinel
Approved revision: no longer in the source branch.
Merge reported by: John A Meinel
Merged at revision: not available
Proposed branch: lp:~jameinel/bzr/2.4-dirstate-invalid-delta-855155
Merge into: lp:bzr/2.4
Diff against target: 134 lines (+61/-8)
4 files modified
bzrlib/dirstate.py (+12/-3)
bzrlib/tests/blackbox/test_uncommit.py (+17/-0)
bzrlib/tests/per_workingtree/test_parents.py (+18/-3)
bzrlib/tests/test_dirstate.py (+14/-2)
To merge this branch: bzr merge lp:~jameinel/bzr/2.4-dirstate-invalid-delta-855155
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Richard Wilbur Approve
Review via email: mp+165984@code.launchpad.net

Commit message

Fix bug #855155. Dirstate.update_basis_by_delta needs to handle adding entries to directories that were otherwise empty (in all trees).

Description of the change

This is proposing Brian's fix for bug #855155 backported to bzr 2.4
https://code.launchpad.net/~slyguy/bzr/bug-855155/+merge/165929

It has 2 tweaks, one is to do a direct update_basis_by_delta test in per_workingtree, which also showed that we needed to be looking up the parent block in the basis tree, not in the 'current' tree.

To post a comment you must log in.
Revision history for this message
Richard Wilbur (richard-wilbur) wrote :

Thanks Brian and John for the patch with the tweaks, and for backporting it to 2.4! Now we just need to land something similar on 2.5 and trunk.
+1

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

I'd just like to doule-check here: this is a valid delta, and incorrect logic in the update routine? It would be easier to be sure of that if there was a test that uses the language of the layer directly: a delta supplied by the test.

review: Needs Information
Revision history for this message
John A Meinel (jameinel) wrote :

It is a valid delta and an invalid update logic.
assertTransitionFromBasisToShape

Is doing this. The delta is simply add a file to a directory that was otherwise empty but must be empty and preferably non-existant in the "current" tree.
Would you be happier with a direct test at the Dirstate level?

The setup would be:

Create a Dirstate with just a tree root in current.
Set the parent tree 0 to a tree with just one directory (a/)
Update the parent tree to a new parent tree with the directory and a file in that directory (a/b)
boom.

This doesn't go boom if the current tree has any records in a (it could be a/c, but likely it would be a/b).
The first patch wouldn't go boom if (a/) exists in the current tree (which is why the setup creates nothing in the current tree)

Revision history for this message
John A Meinel (jameinel) wrote :

I tried to add this test, but it did not explode:

in test_dirstate.py TestUpdateBasisByDelta:

    def test_add_file_in_empty_dir_not_matching_active_state(self):
        state = self.assertUpdate(
                active=[],
                basis=[('dir/', 'dir-id')],
                target=[('dir/', 'dir-id'), ('dir/file', 'file-id')],
                )

The reason it does not explode is because the inventory entry for dir/ in basis has a last-modified revision of 'basis', while the inventory entry in target has a last-modified revision of 'target'.
So the delta produced still includes 'dir/' even though it isn't supposed to change in this test. And we have this bit of code in _update_basis_apply_adds:
            new_kind = new_details[0]
            if new_kind == 'd':
                self._ensure_block(block_index, entry_index, new_path)

So if we see the parent directory in the delta, we will call _ensure_block.

The case we are seeing here is:

1) The directory already exists and is unchanged
2) The file is just being added into that directory
3) The directory is empty, so it does not already have a dirblock existing for it

I'm pretty sure the delta of:
[(None, 'A/B', 'file-id-B',
  InventoryFile('file-id-B', 'B', parent_id='dir-id-A',
  sha1='11111111111111111111111111111111', len=24, revision=new-parent))]

Is accurate for the case of just adding a file to a directory that is otherwise unchanged.

I did push up a tweaked version of a direct Dirstate.update_basis_by_delta test, which requires tweaking the test infrastructure to allow you to specify the exact last-modified-revision for path entries. I think it is reasonably tasteful and does give you a test at the layer the bug exists. (It really is a bug in Dirstate itself, and wouldn't inherently apply to all other tree formats that might exist, it only triggers because we don't force blocks to exist for empty directories.)

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

You have a

2480 #for path, file_id in shape:

in there.

and

2536 import pdb; pdb.set_trace()

other than that - great. Thanks for isolating it; this is complex code and I feel happier knowing we have a layer specific test!

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

Ah, fied in 6060.

review: Approve
Revision history for this message
John A Meinel (jameinel) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrlib/dirstate.py'
--- bzrlib/dirstate.py 2013-05-19 11:45:42 +0000
+++ bzrlib/dirstate.py 2013-05-29 06:05:30 +0000
@@ -1690,9 +1690,18 @@
1690 entry_key = st(dirname, basename, file_id)1690 entry_key = st(dirname, basename, file_id)
1691 block_index, present = self._find_block_index_from_key(entry_key)1691 block_index, present = self._find_block_index_from_key(entry_key)
1692 if not present:1692 if not present:
1693 self._raise_invalid(new_path, file_id,1693 # The block where we want to put the file is not present.
1694 "Unable to find block for this record."1694 # However, it might have just been an empty directory. Look for
1695 " Was the parent added?")1695 # the parent in the basis-so-far before throwing an error.
1696 parent_dir, parent_base = osutils.split(dirname)
1697 parent_block_idx, parent_entry_idx, _, parent_present = \
1698 self._get_block_entry_index(parent_dir, parent_base, 1)
1699 if not parent_present:
1700 self._raise_invalid(new_path, file_id,
1701 "Unable to find block for this record."
1702 " Was the parent added?")
1703 self._ensure_block(parent_block_idx, parent_entry_idx, dirname)
1704
1696 block = self._dirblocks[block_index][1]1705 block = self._dirblocks[block_index][1]
1697 entry_index, present = self._find_entry_index(entry_key, block)1706 entry_index, present = self._find_entry_index(entry_key, block)
1698 if real_add:1707 if real_add:
16991708
=== modified file 'bzrlib/tests/blackbox/test_uncommit.py'
--- bzrlib/tests/blackbox/test_uncommit.py 2010-09-15 09:35:42 +0000
+++ bzrlib/tests/blackbox/test_uncommit.py 2013-05-29 06:05:30 +0000
@@ -280,3 +280,20 @@
280 tree.commit(u'\u1234 message')280 tree.commit(u'\u1234 message')
281 out, err = self.run_bzr('uncommit --force tree', encoding='ascii')281 out, err = self.run_bzr('uncommit --force tree', encoding='ascii')
282 self.assertContainsRe(out, r'\? message')282 self.assertContainsRe(out, r'\? message')
283
284
285class TestInconsistentDelta(TestCaseWithTransport):
286 # See https://bugs.launchpad.net/bzr/+bug/855155
287 # See https://bugs.launchpad.net/bzr/+bug/1100385
288 # bzr uncommit may result in error
289 # 'An inconsistent delta was supplied involving'
290
291 def test_inconsistent_delta(self):
292 # Script taken from https://bugs.launchpad.net/bzr/+bug/855155/comments/26
293 wt = self.make_branch_and_tree('test')
294 self.build_tree(['test/a/', 'test/a/b', 'test/a/c'])
295 wt.add(['a', 'a/b', 'a/c'])
296 wt.commit('initial commit', rev_id='a1')
297 wt.remove(['a/b', 'a/c'])
298 wt.commit('remove b and c', rev_id='a2')
299 self.run_bzr("uncommit --force test")
283300
=== modified file 'bzrlib/tests/per_workingtree/test_parents.py'
--- bzrlib/tests/per_workingtree/test_parents.py 2011-05-08 16:02:52 +0000
+++ bzrlib/tests/per_workingtree/test_parents.py 2013-05-29 06:05:30 +0000
@@ -449,7 +449,7 @@
449 self.add_dir(new_shape, new_revid, 'root-id', None, '')449 self.add_dir(new_shape, new_revid, 'root-id', None, '')
450450
451 def assertTransitionFromBasisToShape(self, basis_shape, basis_revid,451 def assertTransitionFromBasisToShape(self, basis_shape, basis_revid,
452 new_shape, new_revid, extra_parent=None):452 new_shape, new_revid, extra_parent=None, set_current_inventory=True):
453 # set the inventory revision ids.453 # set the inventory revision ids.
454 basis_shape.revision_id = basis_revid454 basis_shape.revision_id = basis_revid
455 new_shape.revision_id = new_revid455 new_shape.revision_id = new_revid
@@ -464,8 +464,9 @@
464 parents.append(extra_parent)464 parents.append(extra_parent)
465 tree.set_parent_ids(parents)465 tree.set_parent_ids(parents)
466 self.fake_up_revision(tree, new_revid, new_shape)466 self.fake_up_revision(tree, new_revid, new_shape)
467 # give tree an inventory of new_shape467 if set_current_inventory:
468 tree._write_inventory(new_shape)468 # give tree an inventory of new_shape
469 tree._write_inventory(new_shape)
469 self.assertDeltaApplicationResultsInExpectedBasis(tree, new_revid,470 self.assertDeltaApplicationResultsInExpectedBasis(tree, new_revid,
470 delta, new_shape)471 delta, new_shape)
471 # The tree should be internally consistent; while this is a moderately472 # The tree should be internally consistent; while this is a moderately
@@ -756,3 +757,17 @@
756 self.add_link(new_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'D')757 self.add_link(new_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'D')
757 self.assertTransitionFromBasisToShape(basis_shape, old_revid,758 self.assertTransitionFromBasisToShape(basis_shape, old_revid,
758 new_shape, new_revid)759 new_shape, new_revid)
760
761 def test_add_files_to_empty_directory(self):
762 old_revid = 'old-parent'
763 basis_shape = Inventory(root_id=None)
764 self.add_dir(basis_shape, old_revid, 'root-id', None, '')
765 self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
766 new_revid = 'new-parent'
767 new_shape = Inventory(root_id=None)
768 self.add_new_root(new_shape, old_revid, new_revid)
769 self.add_dir(new_shape, old_revid, 'dir-id-A', 'root-id', 'A')
770 self.add_file(new_shape, new_revid, 'file-id-B', 'dir-id-A', 'B',
771 '1' * 32, 24)
772 self.assertTransitionFromBasisToShape(basis_shape, old_revid,
773 new_shape, new_revid, set_current_inventory=False)
759774
=== modified file 'bzrlib/tests/test_dirstate.py'
--- bzrlib/tests/test_dirstate.py 2011-05-25 13:44:28 +0000
+++ bzrlib/tests/test_dirstate.py 2013-05-29 06:05:30 +0000
@@ -2477,7 +2477,12 @@
2477 def create_tree_from_shape(self, rev_id, shape):2477 def create_tree_from_shape(self, rev_id, shape):
2478 dir_ids = {'': 'root-id'}2478 dir_ids = {'': 'root-id'}
2479 inv = inventory.Inventory('root-id', rev_id)2479 inv = inventory.Inventory('root-id', rev_id)
2480 for path, file_id in shape:2480 for info in shape:
2481 if len(info) == 2:
2482 path, file_id = info
2483 ie_rev_id = rev_id
2484 else:
2485 path, file_id, ie_rev_id = info
2481 if path == '':2486 if path == '':
2482 # Replace the root entry2487 # Replace the root entry
2483 del inv._byid[inv.root.file_id]2488 del inv._byid[inv.root.file_id]
@@ -2485,7 +2490,7 @@
2485 inv._byid[file_id] = inv.root2490 inv._byid[file_id] = inv.root
2486 dir_ids[''] = file_id2491 dir_ids[''] = file_id
2487 continue2492 continue
2488 inv.add(self.path_to_ie(path, file_id, rev_id, dir_ids))2493 inv.add(self.path_to_ie(path, file_id, ie_rev_id, dir_ids))
2489 return revisiontree.InventoryRevisionTree(_Repo(), inv, rev_id)2494 return revisiontree.InventoryRevisionTree(_Repo(), inv, rev_id)
24902495
2491 def create_empty_dirstate(self):2496 def create_empty_dirstate(self):
@@ -2613,6 +2618,13 @@
2613 target=[('file', 'file-id')],2618 target=[('file', 'file-id')],
2614 )2619 )
26152620
2621 def test_add_file_in_empty_dir_not_matching_active_state(self):
2622 state = self.assertUpdate(
2623 active=[],
2624 basis=[('dir/', 'dir-id')],
2625 target=[('dir/', 'dir-id', 'basis'), ('dir/file', 'file-id')],
2626 )
2627
2616 def test_add_file_missing_in_active_state(self):2628 def test_add_file_missing_in_active_state(self):
2617 state = self.assertUpdate(2629 state = self.assertUpdate(
2618 active=[],2630 active=[],

Subscribers

People subscribed via source and target branches