Merge lp:~jameinel/bzr/2.0.4-change-root-id-494269 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-change-root-id-494269
Merge into: lp:bzr/2.0
Diff against target: 277 lines (+132/-19)
4 files modified
NEWS (+7/-0)
bzrlib/tests/test_revert.py (+12/-1)
bzrlib/tests/test_transform.py (+34/-6)
bzrlib/transform.py (+79/-12)
To merge this branch: bzr merge lp:~jameinel/bzr/2.0.4-change-root-id-494269
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+17174@code.launchpad.net
To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

This patch is done to address bug #494269.

Namely, if a transform included changing the root's file-id, it would fail in a variety of ways. One way is that 'adjust_path' would get called, and it would fail claiming that you can't have more than one root. Aaron fixed some of that in his nested-trees branch, so I'm just cherrypicking that out here.

This means that 'bzr revert' can now handle the root-id changing.

In testing, 'revert' works, but 'update' is still broken, because of bug #504390. I'll start working on that one next.

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

 review: +1

review: Approve

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-08 01:08:40 +0000
3+++ NEWS 2010-01-12 04:33:14 +0000
4@@ -51,6 +51,13 @@
5 changed underneath it (like another autopack). Now concurrent
6 autopackers will properly succeed. (John Arbash Meinel, #495000)
7
8+* ``TreeTransform`` can now handle when a delta says that the file id for
9+ the tree root changes. Rather than trying to rename your working
10+ directory, or failing early saying that you can't have multiple
11+ tree roots. This also fixes ``bzr revert`` when the root id changes.
12+ (Currently ``bzr update`` is still broken.)
13+ (John Arbash Meinel, #494269)
14+
15 * ``_update_current_block`` no longer suppresses exceptions, so ^C at just
16 the right time will get propagated, rather than silently failing to move
17 the block pointer. (John Arbash Meinel, Gareth White, #495023)
18
19=== modified file 'bzrlib/tests/test_revert.py'
20--- bzrlib/tests/test_revert.py 2009-03-23 14:59:43 +0000
21+++ bzrlib/tests/test_revert.py 2010-01-12 04:33:14 +0000
22@@ -1,4 +1,4 @@
23-# Copyright (C) 2006 Canonical Ltd
24+# Copyright (C) 2006, 2007, 2009, 2010 Canonical Ltd
25 #
26 # This program is free software; you can redistribute it and/or modify
27 # it under the terms of the GNU General Public License as published by
28@@ -157,3 +157,14 @@
29 self.failUnlessExists('dir/file1')
30 self.failIfExists('dir/file2')
31 self.assertEqual('dir-id', tree.path2id('dir'))
32+
33+ def test_revert_root_id_change(self):
34+ tree = self.make_branch_and_tree('.')
35+ tree.set_root_id('initial-root-id')
36+ self.build_tree(['file1'])
37+ tree.add(['file1'])
38+ tree.commit('first')
39+ tree.set_root_id('temp-root-id')
40+ self.assertEqual('temp-root-id', tree.get_root_id())
41+ tree.revert()
42+ self.assertEqual('initial-root-id', tree.get_root_id())
43
44=== modified file 'bzrlib/tests/test_transform.py'
45--- bzrlib/tests/test_transform.py 2009-10-14 18:37:13 +0000
46+++ bzrlib/tests/test_transform.py 2010-01-12 04:33:14 +0000
47@@ -1,4 +1,4 @@
48-# Copyright (C) 2006, 2007, 2008 Canonical Ltd
49+# Copyright (C) 2006, 2007, 2008, 2009, 2010 Canonical Ltd
50 #
51 # This program is free software; you can redistribute it and/or modify
52 # it under the terms of the GNU General Public License as published by
53@@ -136,6 +136,36 @@
54 transform.finalize()
55 transform.finalize()
56
57+ def test_change_root_id(self):
58+ transform, root = self.get_transform()
59+ self.assertNotEqual('new-root-id', self.wt.get_root_id())
60+ transform.new_directory('', ROOT_PARENT, 'new-root-id')
61+ transform.delete_contents(root)
62+ transform.unversion_file(root)
63+ transform.fixup_new_roots()
64+ transform.apply()
65+ self.assertEqual('new-root-id', self.wt.get_root_id())
66+
67+ def test_change_root_id_add_files(self):
68+ transform, root = self.get_transform()
69+ self.assertNotEqual('new-root-id', self.wt.get_root_id())
70+ new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
71+ transform.new_file('file', new_trans_id, ['new-contents\n'],
72+ 'new-file-id')
73+ transform.delete_contents(root)
74+ transform.unversion_file(root)
75+ transform.fixup_new_roots()
76+ transform.apply()
77+ self.assertEqual('new-root-id', self.wt.get_root_id())
78+ self.assertEqual('new-file-id', self.wt.path2id('file'))
79+ self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
80+
81+ def test_add_two_roots(self):
82+ transform, root = self.get_transform()
83+ new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
84+ new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
85+ self.assertRaises(ValueError, transform.fixup_new_roots)
86+
87 def test_hardlink(self):
88 self.requireFeature(HardlinkFeature)
89 transform, root = self.get_transform()
90@@ -746,7 +776,7 @@
91 create.apply()
92 transform, root = self.get_transform()
93 transform.adjust_root_path('oldroot', fun)
94- new_root=transform.trans_id_tree_path('')
95+ new_root = transform.trans_id_tree_path('')
96 transform.version_file('new-root', new_root)
97 transform.apply()
98
99@@ -2428,7 +2458,7 @@
100
101 def test_file_content_summary_executable(self):
102 if not osutils.supports_executable():
103- raise TestNotApplicable()
104+ raise tests.TestNotApplicable()
105 preview = self.get_empty_preview()
106 path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
107 preview.set_executability(True, path_id)
108@@ -2443,8 +2473,6 @@
109 self.assertIs(None, summary[3])
110
111 def test_change_executability(self):
112- if not osutils.supports_executable():
113- raise TestNotApplicable()
114 tree = self.make_branch_and_tree('tree')
115 self.build_tree(['tree/path'])
116 tree.add('path')
117@@ -2616,7 +2644,7 @@
118 preview = self.get_empty_preview()
119 root = preview.new_directory('', ROOT_PARENT, 'tree-root')
120 # FIXME: new_directory should mark root.
121- preview.adjust_path('', ROOT_PARENT, root)
122+ preview.fixup_new_roots()
123 preview_tree = preview.get_preview_tree()
124 file_trans_id = preview.new_file('a', preview.root, 'contents',
125 'a-id')
126
127=== modified file 'bzrlib/transform.py'
128--- bzrlib/transform.py 2009-10-28 07:34:55 +0000
129+++ bzrlib/transform.py 2010-01-12 04:33:14 +0000
130@@ -1,4 +1,4 @@
131-# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
132+# Copyright (C) 2006, 2007, 2008, 2009, 2010 Canonical Ltd
133 #
134 # This program is free software; you can redistribute it and/or modify
135 # it under the terms of the GNU General Public License as published by
136@@ -161,14 +161,12 @@
137
138 def adjust_path(self, name, parent, trans_id):
139 """Change the path that is assigned to a transaction id."""
140+ if parent is None:
141+ raise ValueError("Parent trans-id may not be None")
142 if trans_id == self._new_root:
143 raise CantMoveRoot
144 self._new_name[trans_id] = name
145 self._new_parent[trans_id] = parent
146- if parent == ROOT_PARENT:
147- if self._new_root is not None:
148- raise ValueError("Cannot have multiple roots.")
149- self._new_root = trans_id
150
151 def adjust_root_path(self, name, parent):
152 """Emulate moving the root by moving all children, instead.
153@@ -202,6 +200,67 @@
154 self.version_file(old_root_file_id, old_root)
155 self.unversion_file(self._new_root)
156
157+ def fixup_new_roots(self):
158+ """Reinterpret requests to change the root directory
159+
160+ Instead of creating a root directory, or moving an existing directory,
161+ all the attributes and children of the new root are applied to the
162+ existing root directory.
163+
164+ This means that the old root trans-id becomes obsolete, so it is
165+ recommended only to invoke this after the root trans-id has become
166+ irrelevant.
167+ """
168+ new_roots = [k for k, v in self._new_parent.iteritems() if v is
169+ ROOT_PARENT]
170+ if len(new_roots) < 1:
171+ return
172+ if len(new_roots) != 1:
173+ raise ValueError('A tree cannot have two roots!')
174+ if self._new_root is None:
175+ self._new_root = new_roots[0]
176+ return
177+ old_new_root = new_roots[0]
178+ # TODO: What to do if a old_new_root is present, but self._new_root is
179+ # not listed as being removed? This code explicitly unversions
180+ # the old root and versions it with the new file_id. Though that
181+ # seems like an incomplete delta
182+
183+ # unversion the new root's directory.
184+ file_id = self.final_file_id(old_new_root)
185+ if old_new_root in self._new_id:
186+ self.cancel_versioning(old_new_root)
187+ else:
188+ self.unversion_file(old_new_root)
189+ # if, at this stage, root still has an old file_id, zap it so we can
190+ # stick a new one in.
191+ if (self.tree_file_id(self._new_root) is not None and
192+ self._new_root not in self._removed_id):
193+ self.unversion_file(self._new_root)
194+ self.version_file(file_id, self._new_root)
195+
196+ # Now move children of new root into old root directory.
197+ # Ensure all children are registered with the transaction, but don't
198+ # use directly-- some tree children have new parents
199+ list(self.iter_tree_children(old_new_root))
200+ # Move all children of new root into old root directory.
201+ for child in self.by_parent().get(old_new_root, []):
202+ self.adjust_path(self.final_name(child), self._new_root, child)
203+
204+ # Ensure old_new_root has no directory.
205+ if old_new_root in self._new_contents:
206+ self.cancel_creation(old_new_root)
207+ else:
208+ self.delete_contents(old_new_root)
209+
210+ # prevent deletion of root directory.
211+ if self._new_root in self._removed_contents:
212+ self.cancel_deletion(self._new_root)
213+
214+ # destroy path info for old_new_root.
215+ del self._new_parent[old_new_root]
216+ del self._new_name[old_new_root]
217+
218 def trans_id_tree_file_id(self, inventory_id):
219 """Determine the transaction id of a working tree file.
220
221@@ -253,6 +312,8 @@
222
223 def delete_contents(self, trans_id):
224 """Schedule the contents of a path entry for deletion"""
225+ # Ensure that the object exists in the WorkingTree, this will raise an
226+ # exception if there is a problem
227 self.tree_kind(trans_id)
228 self._removed_contents.add(trans_id)
229
230@@ -1075,8 +1136,10 @@
231 if (trans_id in self._limbo_files and
232 trans_id not in self._needs_rename):
233 self._rename_in_limbo([trans_id])
234- self._limbo_children[previous_parent].remove(trans_id)
235- del self._limbo_children_names[previous_parent][previous_name]
236+ if previous_parent != parent:
237+ self._limbo_children[previous_parent].remove(trans_id)
238+ if previous_parent != parent or previous_name != name:
239+ del self._limbo_children_names[previous_parent][previous_name]
240
241 def _rename_in_limbo(self, trans_ids):
242 """Fix limbo names so that the right final path is produced.
243@@ -1542,10 +1605,10 @@
244 child_pb.update('removing file', num, len(tree_paths))
245 full_path = self._tree.abspath(path)
246 if trans_id in self._removed_contents:
247- mover.pre_delete(full_path, os.path.join(self._deletiondir,
248- trans_id))
249- elif trans_id in self._new_name or trans_id in \
250- self._new_parent:
251+ delete_path = os.path.join(self._deletiondir, trans_id)
252+ mover.pre_delete(full_path, delete_path)
253+ elif (trans_id in self._new_name
254+ or trans_id in self._new_parent):
255 try:
256 mover.rename(full_path, self._limbo_name(trans_id))
257 except OSError, e:
258@@ -2636,7 +2699,10 @@
259 parent_trans = ROOT_PARENT
260 else:
261 parent_trans = tt.trans_id_file_id(parent[1])
262- tt.adjust_path(name[1], parent_trans, trans_id)
263+ if parent[0] is None and versioned[0]:
264+ tt.adjust_root_path(name[1], parent_trans)
265+ else:
266+ tt.adjust_path(name[1], parent_trans, trans_id)
267 if executable[0] != executable[1] and kind[1] == "file":
268 tt.set_executability(executable[1], trans_id)
269 if working_tree.supports_content_filtering():
270@@ -2655,6 +2721,7 @@
271 for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
272 deferred_files):
273 tt.create_file(bytes, trans_id, mode_id)
274+ tt.fixup_new_roots()
275 finally:
276 if basis_tree is not None:
277 basis_tree.unlock()

Subscribers

People subscribed via source and target branches