Merge lp:~jameinel/bzr/2.0.4-change-root-id-494269 into lp:bzr/2.0
- 2.0.4-change-root-id-494269
- Merge into 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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert Collins (community) | Approve | ||
Review via email: mp+17174@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote : | # |
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() |
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.