Merge lp:~jameinel/bzr/2.4-update-basis-by-delta-781168 into lp:bzr

Proposed by John A Meinel
Status: Merged
Approved by: Martin Pool
Approved revision: no longer in the source branch.
Merged at revision: 5880
Proposed branch: lp:~jameinel/bzr/2.4-update-basis-by-delta-781168
Merge into: lp:bzr
Diff against target: 212 lines (+85/-46)
3 files modified
bzrlib/dirstate.py (+44/-18)
bzrlib/tests/test_inv.py (+35/-28)
doc/en/release-notes/bzr-2.4.txt (+6/-0)
To merge this branch: bzr merge lp:~jameinel/bzr/2.4-update-basis-by-delta-781168
Reviewer Review Type Date Requested Status
Martin Pool Approve
Review via email: mp+61232@code.launchpad.net

Commit message

Fix bug #781168, and allow WT.update_basis_by_delta to not require the delta makes the basis match the current state.

Description of the change

This changes the WT.update_basis_by_delta code so that it no longer requires that the delta causes the basis tree to match the current state. That was relevant for commit, but it isn't relevant for uncommit, etc.

Thanks to Jonathan Riddell for pairing on this with me.

While working on this, we also discovered that there were edge cases that were *not* handled by the update_basis_by_delta code, it looked like they were accidentally triggered in update_by_delta code because of how the test state was set up. I have more confidence now that things are correct, though there could still be edge cases that we are missing.

To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) wrote :

> raise AssertionError('entry was missing, but now was found')

should that be an InconsistentDelta instead?

Could you log perhaps the entry key or other information so we have a bit of a clue if somebody cites this in a bug report?

I don't understand why the code is being deleted from test_inv.py.

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

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 05/17/2011 02:41 PM, Martin Pool wrote:
>> raise AssertionError('entry was missing, but now was found')
>
> should that be an InconsistentDelta instead?
>
> Could you log perhaps the entry key or other information so we have a bit of a clue if somebody cites this in a bug report?
>
> I don't understand why the code is being deleted from test_inv.py.

That specific one is AssertionError. Specifically, we just checked that
the entry wasn't present with:
entry[0] is None

so if it now shows up as present, something is really wrong with our
data, and it has nothing to do with the delta that was supplied.
I can change it to any other error if you want, but it is "something is
corrupted". I can even take it out. I just was thinking to check
"present" but realized I shouldn't have to.

The code in test_inv.py is part of the "set up trees so that I can call
update_basis_by_delta them". It existed because the old version required
that the WT state matched the new basis state. So it went around
modifying active state to match the delta target. Since we no longer
want that synchronization to apply, I removed it.

This also revealed a bunch of tests that were accidentally passing.
Specifically, I'm guessing that "mutate state to match" was triggering
extra checks that weren't being done in update_basis_by_delta.

For example "test_unicode_file_ids". That tests that you can't add an
Entry that uses a Unicode file_id (rather than a utf-8 str id).
In bzr.dev today, the exception is being raised in "update_by_delta"
*not* "update_basis_by_delta".

The former is called when you mutate the working inventory state, the
latter when you are mutating the basis inventory state.
So the test was accidentally passing in the 'setup' phase of the code,
rather in the code-under-test part of the code.

Does that help you understand?
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk3ScLYACgkQJdeBCYSNAAO29ACfZHDn06e9A/YTrqDUA0mz72Za
6jcAoKurliZDQ9FyO62cq4ei8lzCjWxf
=QL9d
-----END PGP SIGNATURE-----

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

thanks, that's great

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
1=== modified file 'bzrlib/dirstate.py'
2--- bzrlib/dirstate.py 2011-05-11 01:58:47 +0000
3+++ bzrlib/dirstate.py 2011-05-17 13:32:21 +0000
4@@ -1326,6 +1326,14 @@
5 raise
6 return result
7
8+ def _check_delta_is_valid(self, delta):
9+ return list(inventory._check_delta_unique_ids(
10+ inventory._check_delta_unique_old_paths(
11+ inventory._check_delta_unique_new_paths(
12+ inventory._check_delta_ids_match_entry(
13+ inventory._check_delta_ids_are_valid(
14+ inventory._check_delta_new_path_entry_both_or_None(delta)))))))
15+
16 def update_by_delta(self, delta):
17 """Apply an inventory delta to the dirstate for tree 0
18
19@@ -1349,13 +1357,8 @@
20 new_ids = set()
21 # This loop transforms the delta to single atomic operations that can
22 # be executed and validated.
23- for old_path, new_path, file_id, inv_entry in sorted(
24- inventory._check_delta_unique_old_paths(
25- inventory._check_delta_unique_new_paths(
26- inventory._check_delta_ids_match_entry(
27- inventory._check_delta_ids_are_valid(
28- inventory._check_delta_new_path_entry_both_or_None(delta))))),
29- reverse=True):
30+ delta = sorted(self._check_delta_is_valid(delta), reverse=True)
31+ for old_path, new_path, file_id, inv_entry in delta:
32 if (file_id in insertions) or (file_id in removals):
33 raise errors.InconsistentDelta(old_path or new_path, file_id,
34 "repeated file_id")
35@@ -1475,9 +1478,6 @@
36 Note that an exception during the operation of this method will leave
37 the dirstate in a corrupt state where it should not be saved.
38
39- Finally, we expect all changes to be synchronising the basis tree with
40- the working tree.
41-
42 :param new_revid: The new revision id for the trees parent.
43 :param delta: An inventory delta (see apply_inventory_delta) describing
44 the changes from the current left most parent revision to new_revid.
45@@ -1495,7 +1495,7 @@
46
47 self._parents[0] = new_revid
48
49- delta = sorted(delta, reverse=True)
50+ delta = sorted(self._check_delta_is_valid(delta), reverse=True)
51 adds = []
52 changes = []
53 deletes = []
54@@ -1653,7 +1653,17 @@
55 for old_path, new_path, file_id, new_details, real_add in adds:
56 # the entry for this file_id must be in tree 0.
57 entry = self._get_entry(0, file_id, new_path)
58- if entry[0] is None or entry[0][2] != file_id:
59+ if entry[0] is None:
60+ # new_path is not versioned in the active WT state,
61+ # but we are adding it to the basis tree state, we
62+ # need to create a new entry record for it.
63+ dirname, basename = osutils.split(new_path)
64+ entry_key = (dirname, basename, file_id)
65+ _, block = self._find_block(entry_key, add_if_missing=True)
66+ index, _ = self._find_entry_index(entry_key, block)
67+ entry = (entry_key, [DirState.NULL_PARENT_DETAILS]*2)
68+ block.insert(index, entry)
69+ elif entry[0][2] != file_id:
70 self._changes_aborted = True
71 raise errors.InconsistentDelta(new_path, file_id,
72 'working tree does not contain new entry')
73@@ -1719,12 +1729,27 @@
74 raise errors.InconsistentDelta(old_path, file_id,
75 'mismatched file_id in tree 1')
76 if real_delete:
77- if entry[1][0][0] != 'a':
78- self._changes_aborted = True
79- raise errors.InconsistentDelta(old_path, file_id,
80- 'This was marked as a real delete, but the WT state'
81- ' claims that it still exists and is versioned.')
82- del self._dirblocks[block_index][1][entry_index]
83+ if entry[1][0][0] == 'a':
84+ # The file was marked as deleted in the active
85+ # state, and it is now deleted in the basis state,
86+ # so just remove the record entirely
87+ del self._dirblocks[block_index][1][entry_index]
88+ else:
89+ # The basis entry needs to be marked deleted
90+ entry[1][1] = null
91+ # If we are deleting a directory, we need to make sure
92+ # that all of its children are already deleted
93+ block_i, entry_i, d_present, f_present = \
94+ self._get_block_entry_index(old_path, '', 0)
95+ if d_present:
96+ # The dir block is still present in the dirstate; this could
97+ # be due to it being in a parent tree, or a corrupt delta.
98+ for child_entry in self._dirblocks[block_i][1]:
99+ if child_entry[1][1][0] not in ('r', 'a'):
100+ self._changes_aborted = True
101+ raise errors.InconsistentDelta(old_path, entry[0][2],
102+ "The file id was deleted but its children were "
103+ "not deleted.")
104 else:
105 if entry[1][0][0] == 'a':
106 self._changes_aborted = True
107@@ -1740,6 +1765,7 @@
108 del self._dirblocks[block_index][1][entry_index]
109 else:
110 # it is being resurrected here, so blank it out temporarily.
111+ # should be equivalent to entry[1][1] = null
112 self._dirblocks[block_index][1][entry_index][1][1] = null
113
114 def _after_delta_check_parents(self, parents, index):
115
116=== modified file 'bzrlib/tests/test_inv.py'
117--- bzrlib/tests/test_inv.py 2011-05-13 12:51:05 +0000
118+++ bzrlib/tests/test_inv.py 2011-05-17 13:32:21 +0000
119@@ -176,32 +176,6 @@
120 # This reads basis from the repo and puts it into the tree's local
121 # cache, if it has one.
122 tree.set_parent_ids(['basis'])
123- paths = {}
124- parents = set()
125- for old, new, id, entry in delta:
126- if None in (new, entry):
127- continue
128- paths[new] = (entry.file_id, entry.kind)
129- parents.add(osutils.dirname(new))
130- parents = osutils.minimum_path_selection(parents)
131- parents.discard('')
132- # Put place holders in the tree to permit adding the other entries.
133- for pos, parent in enumerate(parents):
134- if not tree.path2id(parent):
135- # add a synthetic directory in the tree so we can can put the
136- # tree0 entries in place for dirstate.
137- tree.add([parent], ["id%d" % pos], ["directory"])
138- if paths:
139- # Many deltas may cause this mini-apply to fail, but we want to see what
140- # the delta application code says, not the prep that we do to deal with
141- # limitations of dirstate's update_basis code.
142- for path, (file_id, kind) in sorted(paths.items()):
143- try:
144- tree.add([path], [file_id], [kind])
145- except (KeyboardInterrupt, SystemExit):
146- raise
147- except:
148- pass
149 finally:
150 tree.unlock()
151 # Fresh lock, reads disk again.
152@@ -586,8 +560,41 @@
153 self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
154 inv, delta)
155
156-
157-class TestInventory(TestCase):
158+ def test_add_file(self):
159+ inv = self.get_empty_inventory()
160+ file1 = inventory.InventoryFile('file-id', 'path', inv.root.file_id)
161+ file1.revision = 'result'
162+ file1.text_size = 0
163+ file1.text_sha1 = ''
164+ delta = [(None, u'path', 'file-id', file1)]
165+ res_inv = self.apply_delta(self, inv, delta)
166+ self.assertEqual('file-id', res_inv['file-id'].file_id)
167+
168+ def test_remove_file(self):
169+ inv = self.get_empty_inventory()
170+ file1 = inventory.InventoryFile('file-id', 'path', inv.root.file_id)
171+ file1.revision = 'result'
172+ file1.text_size = 0
173+ file1.text_sha1 = ''
174+ inv.add(file1)
175+ delta = [(u'path', None, 'file-id', None)]
176+ res_inv = self.apply_delta(self, inv, delta)
177+ self.assertEqual(None, res_inv.path2id('path'))
178+ self.assertRaises(errors.NoSuchId, res_inv.id2path, 'file-id')
179+
180+ def test_rename_file(self):
181+ inv = self.get_empty_inventory()
182+ file1 = inventory.InventoryFile('file-id', 'path', inv.root.file_id)
183+ file1.revision = 'result'
184+ file1.text_size = 0
185+ file1.text_sha1 = ''
186+ inv.add(file1)
187+ file2 = file1.copy()
188+ file2.name = 'path2'
189+ delta = [(u'path', 'path2', 'file-id', file2)]
190+ res_inv = self.apply_delta(self, inv, delta)
191+ self.assertEqual(None, res_inv.path2id('path'))
192+ self.assertEqual('file-id', res_inv.path2id('path2'))
193
194 def test_is_root(self):
195 """Ensure our root-checking code is accurate."""
196
197=== modified file 'doc/en/release-notes/bzr-2.4.txt'
198--- doc/en/release-notes/bzr-2.4.txt 2011-05-17 10:30:37 +0000
199+++ doc/en/release-notes/bzr-2.4.txt 2011-05-17 13:32:21 +0000
200@@ -83,6 +83,12 @@
201 reporting subdirectories that were tree references (in formats that
202 supported them). (John Arbash Meinel, #764677)
203
204+* ``WT.update_basis_by_delta`` no longer requires that the deltas match
205+ the current WT state. This allows ``update_basis_by_delta`` to be used
206+ by more commands than just commit. Updating with a delta allows us to
207+ not load the whole inventory, which can take 10+s with large trees.
208+ (Jonathan Riddell, John Arbash Meinel, #781168)
209+
210
211 Documentation
212 *************