Merge lp:~jameinel/bzr/2.0.4-dirstate-set-root-504390 into lp:bzr/2.0

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

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:
- if self._id_index is not None:
- self._id_index.setdefault(new_id, set()).add(entry[0])

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.

Revision history for this message
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

review: Approve
Revision history for this message
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://enigmail.mozdev.org/

iEYEARECAAYFAktM+a4ACgkQJdeBCYSNAAOQJgCgkOratSV741fn05kvisDdNxNO
sM8An3S9U4N6rv1S0CoyLTQ+RybU+Y7y
=XBQG
-----END PGP SIGNATURE-----

Preview Diff

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

Subscribers

People subscribed via source and target branches