Merge lp:~jameinel/bzr/2.0.4-dirstate-set-root-504390 into lp:bzr/2.0
- 2.0.4-dirstate-set-root-504390
- Merge into 2.0
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | not available | ||||
Proposed branch: | lp:~jameinel/bzr/2.0.4-dirstate-set-root-504390 | ||||
Merge into: | lp:bzr/2.0 | ||||
Diff against target: |
349 lines (+147/-38) 9 files modified
NEWS (+2/-3) bzrlib/dirstate.py (+10/-3) bzrlib/tests/per_workingtree/test_pull.py (+13/-1) bzrlib/tests/per_workingtree/test_set_root_id.py (+18/-2) bzrlib/tests/per_workingtree/test_workingtree.py (+15/-1) bzrlib/tests/test_dirstate.py (+43/-20) bzrlib/tests/test_shelf.py (+24/-1) bzrlib/tests/test_switch.py (+13/-1) bzrlib/workingtree.py (+9/-6) |
||||
To merge this branch: | bzr merge lp:~jameinel/bzr/2.0.4-dirstate-set-root-504390 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert Collins (community) | Approve | ||
Review via email: mp+17253@code.launchpad.net |
Commit message
Description of the change
John A Meinel (jameinel) wrote : | # |
Robert Collins (lifeless) wrote : | # |
review: +1
might be nice to do a _validate on the tree objects in the per-tree
test, just as an extra check that its all ok [only for these changed/new
tests].
-Rob
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Robert Collins wrote:
> Review: Approve
> review: +1
>
> might be nice to do a _validate on the tree objects in the per-tree
> test, just as an extra check that its all ok [only for these changed/new
> tests].
>
> -Rob
>
Done. I also added a quick integrity check of self._id_index, since that
would have also caught the bug.
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkt
sM8An3S9U4N6rv1
=XBQG
-----END PGP SIGNATURE-----
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2010-01-12 07:02:03 +0000 |
3 | +++ NEWS 2010-01-12 22:38:12 +0000 |
4 | @@ -57,9 +57,8 @@ |
5 | * ``TreeTransform`` can now handle when a delta says that the file id for |
6 | the tree root changes. Rather than trying to rename your working |
7 | directory, or failing early saying that you can't have multiple |
8 | - tree roots. This also fixes ``bzr revert`` when the root id changes. |
9 | - (Currently ``bzr update`` is still broken.) |
10 | - (John Arbash Meinel, #494269) |
11 | + tree roots. This also fixes revert, update, and pull when the root id |
12 | + changes. (John Arbash Meinel, #494269, #504390) |
13 | |
14 | * ``_update_current_block`` no longer suppresses exceptions, so ^C at just |
15 | the right time will get propagated, rather than silently failing to move |
16 | |
17 | === modified file 'bzrlib/dirstate.py' |
18 | --- bzrlib/dirstate.py 2009-10-01 00:56:56 +0000 |
19 | +++ bzrlib/dirstate.py 2010-01-12 22:38:11 +0000 |
20 | @@ -1,4 +1,4 @@ |
21 | -# Copyright (C) 2006, 2007, 2008 Canonical Ltd |
22 | +# Copyright (C) 2006-2010 Canonical Ltd |
23 | # |
24 | # This program is free software; you can redistribute it and/or modify |
25 | # it under the terms of the GNU General Public License as published by |
26 | @@ -1997,6 +1997,8 @@ |
27 | entry_index, present = self._find_entry_index(key, block) |
28 | if present: |
29 | entry = self._dirblocks[block_index][1][entry_index] |
30 | + # TODO: We might want to assert that entry[0][2] == |
31 | + # fileid_utf8. |
32 | if entry[1][tree_index][0] in 'fdlt': |
33 | # this is the result we are looking for: the |
34 | # real home of this file_id in this tree. |
35 | @@ -2354,8 +2356,6 @@ |
36 | self.update_minimal(('', '', new_id), 'd', |
37 | path_utf8='', packed_stat=entry[1][0][4]) |
38 | self._dirblock_state = DirState.IN_MEMORY_MODIFIED |
39 | - if self._id_index is not None: |
40 | - self._id_index.setdefault(new_id, set()).add(entry[0]) |
41 | |
42 | def set_parent_trees(self, trees, ghosts): |
43 | """Set the parent trees for the dirstate. |
44 | @@ -3013,6 +3013,13 @@ |
45 | if absent_positions == tree_count: |
46 | raise AssertionError( |
47 | "entry %r has no data for any tree." % (entry,)) |
48 | + if self._id_index is not None: |
49 | + for file_id, entry_keys in self._id_index.iteritems(): |
50 | + for entry_key in entry_keys: |
51 | + if entry_key[2] != file_id: |
52 | + raise AssertionError( |
53 | + 'file_id %r did not match entry key %s' |
54 | + % (file_id, entry_key)) |
55 | |
56 | def _wipe_state(self): |
57 | """Forget all state information about the dirstate.""" |
58 | |
59 | === modified file 'bzrlib/tests/per_workingtree/test_pull.py' |
60 | --- bzrlib/tests/per_workingtree/test_pull.py 2009-07-10 07:14:02 +0000 |
61 | +++ bzrlib/tests/per_workingtree/test_pull.py 2010-01-12 22:38:12 +0000 |
62 | @@ -1,4 +1,4 @@ |
63 | -# Copyright (C) 2006 Canonical Ltd |
64 | +# Copyright (C) 2006, 2007, 2009, 2010 Canonical Ltd |
65 | # Authors: Robert Collins <robert.collins@canonical.com> |
66 | # |
67 | # This program is free software; you can redistribute it and/or modify |
68 | @@ -56,3 +56,15 @@ |
69 | tree_b.pull(tree_a.branch) |
70 | self.assertFileEqual('contents of from/file\n', 'to/file') |
71 | |
72 | + def test_pull_changes_root_id(self): |
73 | + tree = self.make_branch_and_tree('from') |
74 | + tree.set_root_id('first_root_id') |
75 | + self.build_tree(['from/file']) |
76 | + tree.add(['file']) |
77 | + tree.commit('first') |
78 | + to_tree = tree.bzrdir.sprout('to').open_workingtree() |
79 | + self.assertEqual('first_root_id', to_tree.get_root_id()) |
80 | + tree.set_root_id('second_root_id') |
81 | + tree.commit('second') |
82 | + to_tree.pull(tree.branch) |
83 | + self.assertEqual('second_root_id', to_tree.get_root_id()) |
84 | |
85 | === modified file 'bzrlib/tests/per_workingtree/test_set_root_id.py' |
86 | --- bzrlib/tests/per_workingtree/test_set_root_id.py 2009-09-10 06:32:42 +0000 |
87 | +++ bzrlib/tests/per_workingtree/test_set_root_id.py 2010-01-12 22:38:12 +0000 |
88 | @@ -1,4 +1,4 @@ |
89 | -# Copyright (C) 2006 Canonical Ltd |
90 | +# Copyright (C) 2006-2010 Canonical Ltd |
91 | # |
92 | # This program is free software; you can redistribute it and/or modify |
93 | # it under the terms of the GNU General Public License as published by |
94 | @@ -16,7 +16,7 @@ |
95 | |
96 | """Tests for WorkingTree.set_root_id""" |
97 | |
98 | -from bzrlib import inventory |
99 | +from bzrlib import errors, inventory |
100 | from bzrlib.tests.per_workingtree import TestCaseWithWorkingTree |
101 | |
102 | |
103 | @@ -48,3 +48,19 @@ |
104 | # should still be retained |
105 | tree = tree.bzrdir.open_workingtree() |
106 | self.assertEqual(root_id, tree.get_root_id()) |
107 | + tree._validate() |
108 | + |
109 | + def test_set_root_id(self): |
110 | + tree = self.make_branch_and_tree('.') |
111 | + tree.lock_write() |
112 | + self.addCleanup(tree.unlock) |
113 | + orig_root_id = tree.get_root_id() |
114 | + self.assertNotEqual('custom-root-id', orig_root_id) |
115 | + self.assertEqual('', tree.id2path(orig_root_id)) |
116 | + self.assertRaises(errors.NoSuchId, tree.id2path, 'custom-root-id') |
117 | + tree.set_root_id('custom-root-id') |
118 | + self.assertEqual('custom-root-id', tree.get_root_id()) |
119 | + self.assertEqual('custom-root-id', tree.path2id('')) |
120 | + self.assertEqual('', tree.id2path('custom-root-id')) |
121 | + self.assertRaises(errors.NoSuchId, tree.id2path, orig_root_id) |
122 | + tree._validate() |
123 | |
124 | === modified file 'bzrlib/tests/per_workingtree/test_workingtree.py' |
125 | --- bzrlib/tests/per_workingtree/test_workingtree.py 2009-08-20 04:09:58 +0000 |
126 | +++ bzrlib/tests/per_workingtree/test_workingtree.py 2010-01-12 22:38:11 +0000 |
127 | @@ -1,4 +1,4 @@ |
128 | -# Copyright (C) 2005, 2006, 2007 Canonical Ltd |
129 | +# Copyright (C) 2006, 2007, 2008, 2009, 2010 Canonical Ltd |
130 | # Authors: Robert Collins <robert.collins@canonical.com> |
131 | # and others |
132 | # |
133 | @@ -478,6 +478,20 @@ |
134 | self.assertEqual(wt.get_root_id(), checkout.get_root_id()) |
135 | self.assertNotEqual(None, wt.get_root_id()) |
136 | |
137 | + def test_update_sets_updated_root_id(self): |
138 | + wt = self.make_branch_and_tree('tree') |
139 | + wt.set_root_id('first_root_id') |
140 | + self.assertEqual('first_root_id', wt.get_root_id()) |
141 | + self.build_tree(['tree/file']) |
142 | + wt.add(['file']) |
143 | + wt.commit('first') |
144 | + co = wt.branch.create_checkout('checkout') |
145 | + wt.set_root_id('second_root_id') |
146 | + wt.commit('second') |
147 | + self.assertEqual('second_root_id', wt.get_root_id()) |
148 | + self.assertEqual(0, co.update()) |
149 | + self.assertEqual('second_root_id', co.get_root_id()) |
150 | + |
151 | def test_update_returns_conflict_count(self): |
152 | # working tree formats from the meta-dir format and newer support |
153 | # setting the last revision on a tree independently of that on the |
154 | |
155 | === modified file 'bzrlib/tests/test_dirstate.py' |
156 | --- bzrlib/tests/test_dirstate.py 2009-08-17 03:33:56 +0000 |
157 | +++ bzrlib/tests/test_dirstate.py 2010-01-12 22:38:11 +0000 |
158 | @@ -1,4 +1,4 @@ |
159 | -# Copyright (C) 2006, 2007 Canonical Ltd |
160 | +# Copyright (C) 2006-2010 Canonical Ltd |
161 | # |
162 | # This program is free software; you can redistribute it and/or modify |
163 | # it under the terms of the GNU General Public License as published by |
164 | @@ -873,15 +873,23 @@ |
165 | state = dirstate.DirState.initialize('dirstate') |
166 | try: |
167 | # check precondition to be sure the state does change appropriately. |
168 | - self.assertEqual( |
169 | - [(('', '', 'TREE_ROOT'), [('d', '', 0, False, |
170 | - 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])], |
171 | - list(state._iter_entries())) |
172 | - state.set_path_id('', 'foobarbaz') |
173 | - expected_rows = [ |
174 | - (('', '', 'foobarbaz'), [('d', '', 0, False, |
175 | - 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])] |
176 | + root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)]) |
177 | + self.assertEqual([root_entry], list(state._iter_entries())) |
178 | + self.assertEqual(root_entry, state._get_entry(0, path_utf8='')) |
179 | + self.assertEqual(root_entry, |
180 | + state._get_entry(0, fileid_utf8='TREE_ROOT')) |
181 | + self.assertEqual((None, None), |
182 | + state._get_entry(0, fileid_utf8='second-root-id')) |
183 | + state.set_path_id('', 'second-root-id') |
184 | + new_root_entry = (('', '', 'second-root-id'), |
185 | + [('d', '', 0, False, 'x'*32)]) |
186 | + expected_rows = [new_root_entry] |
187 | self.assertEqual(expected_rows, list(state._iter_entries())) |
188 | + self.assertEqual(new_root_entry, state._get_entry(0, path_utf8='')) |
189 | + self.assertEqual(new_root_entry, |
190 | + state._get_entry(0, fileid_utf8='second-root-id')) |
191 | + self.assertEqual((None, None), |
192 | + state._get_entry(0, fileid_utf8='TREE_ROOT')) |
193 | # should work across save too |
194 | state.save() |
195 | finally: |
196 | @@ -905,21 +913,36 @@ |
197 | state._validate() |
198 | try: |
199 | state.set_parent_trees([('parent-revid', rt)], ghosts=[]) |
200 | - state.set_path_id('', 'foobarbaz') |
201 | + root_entry = (('', '', 'TREE_ROOT'), |
202 | + [('d', '', 0, False, 'x'*32), |
203 | + ('d', '', 0, False, 'parent-revid')]) |
204 | + self.assertEqual(root_entry, state._get_entry(0, path_utf8='')) |
205 | + self.assertEqual(root_entry, |
206 | + state._get_entry(0, fileid_utf8='TREE_ROOT')) |
207 | + self.assertEqual((None, None), |
208 | + state._get_entry(0, fileid_utf8='Asecond-root-id')) |
209 | + state.set_path_id('', 'Asecond-root-id') |
210 | state._validate() |
211 | # now see that it is what we expected |
212 | - expected_rows = [ |
213 | - (('', '', 'TREE_ROOT'), |
214 | - [('a', '', 0, False, ''), |
215 | - ('d', '', 0, False, 'parent-revid'), |
216 | - ]), |
217 | - (('', '', 'foobarbaz'), |
218 | - [('d', '', 0, False, ''), |
219 | - ('a', '', 0, False, ''), |
220 | - ]), |
221 | - ] |
222 | + old_root_entry = (('', '', 'TREE_ROOT'), |
223 | + [('a', '', 0, False, ''), |
224 | + ('d', '', 0, False, 'parent-revid')]) |
225 | + new_root_entry = (('', '', 'Asecond-root-id'), |
226 | + [('d', '', 0, False, ''), |
227 | + ('a', '', 0, False, '')]) |
228 | + expected_rows = [new_root_entry, old_root_entry] |
229 | state._validate() |
230 | self.assertEqual(expected_rows, list(state._iter_entries())) |
231 | + self.assertEqual(new_root_entry, state._get_entry(0, path_utf8='')) |
232 | + self.assertEqual(old_root_entry, state._get_entry(1, path_utf8='')) |
233 | + self.assertEqual((None, None), |
234 | + state._get_entry(0, fileid_utf8='TREE_ROOT')) |
235 | + self.assertEqual(old_root_entry, |
236 | + state._get_entry(1, fileid_utf8='TREE_ROOT')) |
237 | + self.assertEqual(new_root_entry, |
238 | + state._get_entry(0, fileid_utf8='Asecond-root-id')) |
239 | + self.assertEqual((None, None), |
240 | + state._get_entry(1, fileid_utf8='Asecond-root-id')) |
241 | # should work across save too |
242 | state.save() |
243 | finally: |
244 | |
245 | === modified file 'bzrlib/tests/test_shelf.py' |
246 | --- bzrlib/tests/test_shelf.py 2009-09-10 06:32:42 +0000 |
247 | +++ bzrlib/tests/test_shelf.py 2010-01-12 22:38:12 +0000 |
248 | @@ -1,4 +1,4 @@ |
249 | -# Copyright (C) 2008 Canonical Ltd |
250 | +# Copyright (C) 2008, 2009, 2010 Canonical Ltd |
251 | # |
252 | # This program is free software; you can redistribute it and/or modify |
253 | # it under the terms of the GNU General Public License as published by |
254 | @@ -108,6 +108,29 @@ |
255 | creator.shelve_change(('rename', 'baz-id', 'foo/baz', 'bar/baz')) |
256 | self.check_shelve_move(creator, tree) |
257 | |
258 | + def test_shelve_changed_root_id(self): |
259 | + tree = self.make_branch_and_tree('.') |
260 | + self.build_tree(['foo']) |
261 | + tree.set_root_id('first-root-id') |
262 | + tree.add(['foo'], ['foo-id']) |
263 | + tree.commit('foo') |
264 | + tree.set_root_id('second-root-id') |
265 | + tree.lock_tree_write() |
266 | + self.addCleanup(tree.unlock) |
267 | + creator = shelf.ShelfCreator(tree, tree.basis_tree()) |
268 | + self.addCleanup(creator.finalize) |
269 | + self.expectFailure('shelf doesn\'t support shelving root changes yet', |
270 | + self.assertEqual, [ |
271 | + ('delete file', 'first-root-id', 'directory', ''), |
272 | + ('add file', 'second-root-id', 'directory', ''), |
273 | + ('rename', 'foo-id', u'foo', u'foo'), |
274 | + ], list(creator.iter_shelvable())) |
275 | + |
276 | + self.assertEqual([('delete file', 'first-root-id', 'directory', ''), |
277 | + ('add file', 'second-root-id', 'directory', ''), |
278 | + ('rename', 'foo-id', u'foo', u'foo'), |
279 | + ], list(creator.iter_shelvable())) |
280 | + |
281 | def assertShelvedFileEqual(self, expected_content, creator, file_id): |
282 | s_trans_id = creator.shelf_transform.trans_id_file_id(file_id) |
283 | shelf_file = creator.shelf_transform._limbo_name(s_trans_id) |
284 | |
285 | === modified file 'bzrlib/tests/test_switch.py' |
286 | --- bzrlib/tests/test_switch.py 2009-05-07 05:08:46 +0000 |
287 | +++ bzrlib/tests/test_switch.py 2010-01-12 22:38:12 +0000 |
288 | @@ -1,4 +1,4 @@ |
289 | -# Copyright (C) 2007 Canonical Ltd |
290 | +# Copyright (C) 2007-2010 Canonical Ltd |
291 | # |
292 | # This program is free software; you can redistribute it and/or modify |
293 | # it under the terms of the GNU General Public License as published by |
294 | @@ -100,6 +100,18 @@ |
295 | self.assertContainsRe(str(err), |
296 | "Pending merges must be committed or reverted before using switch") |
297 | |
298 | + def test_switch_changing_root_id(self): |
299 | + tree = self._setup_tree() |
300 | + tree2 = self.make_branch_and_tree('tree-2') |
301 | + tree2.set_root_id('custom-root-id') |
302 | + self.build_tree(['tree-2/file-2']) |
303 | + tree2.add(['file-2']) |
304 | + tree2.commit('rev1b') |
305 | + checkout = tree.branch.create_checkout('checkout', |
306 | + lightweight=self.lightweight) |
307 | + switch.switch(checkout.bzrdir, tree2.branch) |
308 | + self.assertEqual('custom-root-id', tree2.get_root_id()) |
309 | + |
310 | |
311 | class TestSwitchHeavyweight(TestSwitch): |
312 | |
313 | |
314 | === modified file 'bzrlib/workingtree.py' |
315 | --- bzrlib/workingtree.py 2009-08-26 05:38:16 +0000 |
316 | +++ bzrlib/workingtree.py 2010-01-12 22:38:12 +0000 |
317 | @@ -1,4 +1,4 @@ |
318 | -# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd |
319 | +# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Canonical Ltd |
320 | # |
321 | # This program is free software; you can redistribute it and/or modify |
322 | # it under the terms of the GNU General Public License as published by |
323 | @@ -1624,9 +1624,10 @@ |
324 | this_tree=self, |
325 | pb=pb, |
326 | change_reporter=change_reporter) |
327 | - if (basis_tree.inventory.root is None and |
328 | - new_basis_tree.inventory.root is not None): |
329 | - self.set_root_id(new_basis_tree.get_root_id()) |
330 | + basis_root_id = basis_tree.get_root_id() |
331 | + new_root_id = new_basis_tree.get_root_id() |
332 | + if basis_root_id != new_root_id: |
333 | + self.set_root_id(new_root_id) |
334 | finally: |
335 | pb.finished() |
336 | basis_tree.unlock() |
337 | @@ -2245,8 +2246,10 @@ |
338 | basis.lock_read() |
339 | try: |
340 | to_tree = self.branch.basis_tree() |
341 | - if basis.inventory.root is None: |
342 | - self.set_root_id(to_tree.get_root_id()) |
343 | + to_root_id = to_tree.get_root_id() |
344 | + if (basis.inventory.root is None |
345 | + or basis.inventory.root.file_id != to_root_id): |
346 | + self.set_root_id(to_root_id) |
347 | self.flush() |
348 | result += merge.merge_inner( |
349 | self.branch, |
This is a follow up to my earlier patch for handling root-id changes in 'bzr revert'.
I don't quite understand why "merge. merge_inner( )" doesn't handle setting the root id, but WT.revert() *does*. But for now, I'll live with it, and set the root id where the existing code was doing it.
The primary difference for pull and update is that we had lines saying "if basis.inventory .root is None: set_root_id()", and this changes those lines to use "if basis.inventory .root != new_root: set_root_id()".
This exposed a bug in the dirstate logic. It turned out that "set_path_id()" was doing: index.setdefaul t(new_id, set()). add(entry[ 0])
- if self._id_index is not None:
- self._id_
Which looked correct at first (and second) glances. However, it turns out that 'entry' at that point is the *old* entry. ('', '', 'TREE_ROOT') not the new entry ('', '', 'second-file-id').
What that meant was that when we did the _id_index lookup, we might get back an entry key whose file-id *doesn't* match the actual file-id. And depending on the ordering of iter(set()) we would see that the entry that we thought matched our key was marked absent in the corresponding tree.
update_minimal() had already sorted out what needed to be done to the _id_index, so the line was both superfluous and wrong (I didn't need to change it to the new entry).
The net effect is that with this patch, 'bzr pull', 'bzr revert', and 'bzr update' with a root-id change will all leave the tree with the correct root-id.