Merge lp:~jelmer/brz/transform-file-id into lp:brz/3.1
- transform-file-id
- Merge into 3.1
Proposed by
Jelmer Vernooij
Status: | Superseded |
---|---|
Proposed branch: | lp:~jelmer/brz/transform-file-id |
Merge into: | lp:brz/3.1 |
Diff against target: |
2570 lines (+1291/-910) 16 files modified
breezy/bzr/inventorytree.py (+3/-3) breezy/bzr/tests/__init__.py (+1/-0) breezy/bzr/tests/test_transform.py (+49/-0) breezy/bzr/transform.py (+903/-0) breezy/bzr/workingtree.py (+2/-2) breezy/errors.py (+0/-16) breezy/git/transform.py (+275/-0) breezy/git/tree.py (+6/-7) breezy/git/workingtree.py (+2/-6) breezy/merge.py (+6/-11) breezy/tests/per_tree/test_path_content_summary.py (+4/-2) breezy/tests/per_tree/test_symlinks.py (+1/-1) breezy/tests/per_workingtree/test_transform.py (+3/-2) breezy/tests/test_errors.py (+0/-6) breezy/tests/test_transform.py (+13/-34) breezy/transform.py (+23/-820) |
To merge this branch: | bzr merge lp:~jelmer/brz/transform-file-id |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jelmer Vernooij | Approve | ||
Review via email: mp+386536@code.launchpad.net |
This proposal has been superseded by a proposal from 2020-07-05.
Commit message
Split out git and bzr-specific transforms.
Description of the change
Split out git and bzr-specific transforms.
To post a comment you must log in.
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Revision history for this message
Jelmer Vernooij (jelmer) : | # |
review:
Approve
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Merging failed
https:/
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Merging failed
https:/
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
https:/
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
https:/
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
https:/
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'breezy/bzr/inventorytree.py' | |||
2 | --- breezy/bzr/inventorytree.py 2020-07-04 01:15:59 +0000 | |||
3 | +++ breezy/bzr/inventorytree.py 2020-07-05 13:48:17 +0000 | |||
4 | @@ -325,7 +325,7 @@ | |||
5 | 325 | return last_revision | 325 | return last_revision |
6 | 326 | 326 | ||
7 | 327 | def preview_transform(self, pb=None): | 327 | def preview_transform(self, pb=None): |
9 | 328 | from ..transform import TransformPreview | 328 | from .transform import TransformPreview |
10 | 329 | return TransformPreview(self, pb=pb) | 329 | return TransformPreview(self, pb=pb) |
11 | 330 | 330 | ||
12 | 331 | 331 | ||
13 | @@ -510,8 +510,8 @@ | |||
14 | 510 | self.set_parent_trees([(new_revid, rev_tree)]) | 510 | self.set_parent_trees([(new_revid, rev_tree)]) |
15 | 511 | 511 | ||
16 | 512 | def transform(self, pb=None): | 512 | def transform(self, pb=None): |
19 | 513 | from ..transform import TreeTransform | 513 | from .transform import InventoryTreeTransform |
20 | 514 | return TreeTransform(self, pb=pb) | 514 | return InventoryTreeTransform(self, pb=pb) |
21 | 515 | 515 | ||
22 | 516 | 516 | ||
23 | 517 | class _SmartAddHelper(object): | 517 | class _SmartAddHelper(object): |
24 | 518 | 518 | ||
25 | === modified file 'breezy/bzr/tests/__init__.py' | |||
26 | --- breezy/bzr/tests/__init__.py 2020-06-25 21:13:23 +0000 | |||
27 | +++ breezy/bzr/tests/__init__.py 2020-07-05 13:48:17 +0000 | |||
28 | @@ -81,6 +81,7 @@ | |||
29 | 81 | 'test_serializer', | 81 | 'test_serializer', |
30 | 82 | 'test_tag', | 82 | 'test_tag', |
31 | 83 | 'test_testament', | 83 | 'test_testament', |
32 | 84 | 'test_transform', | ||
33 | 84 | 'test_versionedfile', | 85 | 'test_versionedfile', |
34 | 85 | 'test_vf_search', | 86 | 'test_vf_search', |
35 | 86 | 'test_vfs_ratchet', | 87 | 'test_vfs_ratchet', |
36 | 87 | 88 | ||
37 | === added file 'breezy/bzr/tests/test_transform.py' | |||
38 | --- breezy/bzr/tests/test_transform.py 1970-01-01 00:00:00 +0000 | |||
39 | +++ breezy/bzr/tests/test_transform.py 2020-07-05 13:48:17 +0000 | |||
40 | @@ -0,0 +1,49 @@ | |||
41 | 1 | # Copyright (C) 2006-2012, 2016 Canonical Ltd | ||
42 | 2 | # | ||
43 | 3 | # This program is free software; you can redistribute it and/or modify | ||
44 | 4 | # it under the terms of the GNU General Public License as published by | ||
45 | 5 | # the Free Software Foundation; either version 2 of the License, or | ||
46 | 6 | # (at your option) any later version. | ||
47 | 7 | # | ||
48 | 8 | # This program is distributed in the hope that it will be useful, | ||
49 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
50 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
51 | 11 | # GNU General Public License for more details. | ||
52 | 12 | # | ||
53 | 13 | # You should have received a copy of the GNU General Public License | ||
54 | 14 | # along with this program; if not, write to the Free Software | ||
55 | 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
56 | 16 | |||
57 | 17 | from . import TestCaseWithTransport | ||
58 | 18 | |||
59 | 19 | |||
60 | 20 | class TestInventoryAltered(TestCaseWithTransport): | ||
61 | 21 | |||
62 | 22 | def test_inventory_altered_unchanged(self): | ||
63 | 23 | tree = self.make_branch_and_tree('tree') | ||
64 | 24 | self.build_tree(['tree/foo']) | ||
65 | 25 | tree.add('foo', b'foo-id') | ||
66 | 26 | with tree.preview_transform() as tt: | ||
67 | 27 | self.assertEqual([], tt._inventory_altered()) | ||
68 | 28 | |||
69 | 29 | def test_inventory_altered_changed_parent_id(self): | ||
70 | 30 | tree = self.make_branch_and_tree('tree') | ||
71 | 31 | self.build_tree(['tree/foo']) | ||
72 | 32 | tree.add('foo', b'foo-id') | ||
73 | 33 | with tree.preview_transform() as tt: | ||
74 | 34 | tt.unversion_file(tt.root) | ||
75 | 35 | tt.version_file(tt.root, file_id=b'new-id') | ||
76 | 36 | foo_trans_id = tt.trans_id_tree_path('foo') | ||
77 | 37 | foo_tuple = ('foo', foo_trans_id) | ||
78 | 38 | root_tuple = ('', tt.root) | ||
79 | 39 | self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered()) | ||
80 | 40 | |||
81 | 41 | def test_inventory_altered_noop_changed_parent_id(self): | ||
82 | 42 | tree = self.make_branch_and_tree('tree') | ||
83 | 43 | self.build_tree(['tree/foo']) | ||
84 | 44 | tree.add('foo', b'foo-id') | ||
85 | 45 | with tree.preview_transform() as tt: | ||
86 | 46 | tt.unversion_file(tt.root) | ||
87 | 47 | tt.version_file(tt.root, file_id=tree.path2id('')) | ||
88 | 48 | tt.trans_id_tree_path('foo') | ||
89 | 49 | self.assertEqual([], tt._inventory_altered()) | ||
90 | 0 | 50 | ||
91 | === added file 'breezy/bzr/transform.py' | |||
92 | --- breezy/bzr/transform.py 1970-01-01 00:00:00 +0000 | |||
93 | +++ breezy/bzr/transform.py 2020-07-05 13:48:17 +0000 | |||
94 | @@ -0,0 +1,903 @@ | |||
95 | 1 | # Copyright (C) 2006-2011 Canonical Ltd | ||
96 | 2 | # Copyright (C) 2020 Breezy Developers | ||
97 | 3 | # | ||
98 | 4 | # This program is free software; you can redistribute it and/or modify | ||
99 | 5 | # it under the terms of the GNU General Public License as published by | ||
100 | 6 | # the Free Software Foundation; either version 2 of the License, or | ||
101 | 7 | # (at your option) any later version. | ||
102 | 8 | # | ||
103 | 9 | # This program is distributed in the hope that it will be useful, | ||
104 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
105 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
106 | 12 | # GNU General Public License for more details. | ||
107 | 13 | # | ||
108 | 14 | # You should have received a copy of the GNU General Public License | ||
109 | 15 | # along with this program; if not, write to the Free Software | ||
110 | 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
111 | 17 | |||
112 | 18 | from __future__ import absolute_import | ||
113 | 19 | |||
114 | 20 | import errno | ||
115 | 21 | import os | ||
116 | 22 | from stat import S_IEXEC | ||
117 | 23 | |||
118 | 24 | from .. import ( | ||
119 | 25 | annotate, | ||
120 | 26 | errors, | ||
121 | 27 | lock, | ||
122 | 28 | osutils, | ||
123 | 29 | revision as _mod_revision, | ||
124 | 30 | tree, | ||
125 | 31 | ui, | ||
126 | 32 | ) | ||
127 | 33 | |||
128 | 34 | from ..i18n import gettext | ||
129 | 35 | from ..mutabletree import MutableTree | ||
130 | 36 | from ..sixish import text_type, viewvalues, viewitems | ||
131 | 37 | from ..transform import ( | ||
132 | 38 | ROOT_PARENT, | ||
133 | 39 | TreeTransform, | ||
134 | 40 | _FileMover, | ||
135 | 41 | _TransformResults, | ||
136 | 42 | DiskTreeTransform, | ||
137 | 43 | joinpath, | ||
138 | 44 | NoFinalPath, | ||
139 | 45 | FinalPaths, | ||
140 | 46 | unique_add, | ||
141 | 47 | TransformRenameFailed, | ||
142 | 48 | ) | ||
143 | 49 | from . import ( | ||
144 | 50 | inventory, | ||
145 | 51 | inventorytree, | ||
146 | 52 | ) | ||
147 | 53 | |||
148 | 54 | |||
149 | 55 | class InventoryTreeTransform(TreeTransform): | ||
150 | 56 | """Tree transform for Bazaar trees.""" | ||
151 | 57 | |||
152 | 58 | def version_file(self, trans_id, file_id=None): | ||
153 | 59 | """Schedule a file to become versioned.""" | ||
154 | 60 | if file_id is None: | ||
155 | 61 | raise ValueError() | ||
156 | 62 | unique_add(self._new_id, trans_id, file_id) | ||
157 | 63 | unique_add(self._r_new_id, file_id, trans_id) | ||
158 | 64 | |||
159 | 65 | def cancel_versioning(self, trans_id): | ||
160 | 66 | """Undo a previous versioning of a file""" | ||
161 | 67 | file_id = self._new_id[trans_id] | ||
162 | 68 | del self._new_id[trans_id] | ||
163 | 69 | del self._r_new_id[file_id] | ||
164 | 70 | |||
165 | 71 | def _duplicate_ids(self): | ||
166 | 72 | """Each inventory id may only be used once""" | ||
167 | 73 | conflicts = [] | ||
168 | 74 | try: | ||
169 | 75 | all_ids = self._tree.all_file_ids() | ||
170 | 76 | except errors.UnsupportedOperation: | ||
171 | 77 | # it's okay for non-file-id trees to raise UnsupportedOperation. | ||
172 | 78 | return [] | ||
173 | 79 | removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in | ||
174 | 80 | self._removed_id)) | ||
175 | 81 | active_tree_ids = all_ids.difference(removed_tree_ids) | ||
176 | 82 | for trans_id, file_id in viewitems(self._new_id): | ||
177 | 83 | if file_id in active_tree_ids: | ||
178 | 84 | path = self._tree.id2path(file_id) | ||
179 | 85 | old_trans_id = self.trans_id_tree_path(path) | ||
180 | 86 | conflicts.append(('duplicate id', old_trans_id, trans_id)) | ||
181 | 87 | return conflicts | ||
182 | 88 | |||
183 | 89 | def find_conflicts(self): | ||
184 | 90 | conflicts = super(InventoryTreeTransform, self).find_conflicts() | ||
185 | 91 | conflicts.extend(self._duplicate_ids()) | ||
186 | 92 | return conflicts | ||
187 | 93 | |||
188 | 94 | def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None): | ||
189 | 95 | """Apply all changes to the inventory and filesystem. | ||
190 | 96 | |||
191 | 97 | If filesystem or inventory conflicts are present, MalformedTransform | ||
192 | 98 | will be thrown. | ||
193 | 99 | |||
194 | 100 | If apply succeeds, finalize is not necessary. | ||
195 | 101 | |||
196 | 102 | :param no_conflicts: if True, the caller guarantees there are no | ||
197 | 103 | conflicts, so no check is made. | ||
198 | 104 | :param precomputed_delta: An inventory delta to use instead of | ||
199 | 105 | calculating one. | ||
200 | 106 | :param _mover: Supply an alternate FileMover, for testing | ||
201 | 107 | """ | ||
202 | 108 | for hook in MutableTree.hooks['pre_transform']: | ||
203 | 109 | hook(self._tree, self) | ||
204 | 110 | if not no_conflicts: | ||
205 | 111 | self._check_malformed() | ||
206 | 112 | with ui.ui_factory.nested_progress_bar() as child_pb: | ||
207 | 113 | if precomputed_delta is None: | ||
208 | 114 | child_pb.update(gettext('Apply phase'), 0, 2) | ||
209 | 115 | inventory_delta = self._generate_inventory_delta() | ||
210 | 116 | offset = 1 | ||
211 | 117 | else: | ||
212 | 118 | inventory_delta = precomputed_delta | ||
213 | 119 | offset = 0 | ||
214 | 120 | if _mover is None: | ||
215 | 121 | mover = _FileMover() | ||
216 | 122 | else: | ||
217 | 123 | mover = _mover | ||
218 | 124 | try: | ||
219 | 125 | child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset) | ||
220 | 126 | self._apply_removals(mover) | ||
221 | 127 | child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset) | ||
222 | 128 | modified_paths = self._apply_insertions(mover) | ||
223 | 129 | except BaseException: | ||
224 | 130 | mover.rollback() | ||
225 | 131 | raise | ||
226 | 132 | else: | ||
227 | 133 | mover.apply_deletions() | ||
228 | 134 | if self.final_file_id(self.root) is None: | ||
229 | 135 | inventory_delta = [e for e in inventory_delta if e[0] != ''] | ||
230 | 136 | self._tree.apply_inventory_delta(inventory_delta) | ||
231 | 137 | self._apply_observed_sha1s() | ||
232 | 138 | self._done = True | ||
233 | 139 | self.finalize() | ||
234 | 140 | return _TransformResults(modified_paths, self.rename_count) | ||
235 | 141 | |||
236 | 142 | def _apply_removals(self, mover): | ||
237 | 143 | """Perform tree operations that remove directory/inventory names. | ||
238 | 144 | |||
239 | 145 | That is, delete files that are to be deleted, and put any files that | ||
240 | 146 | need renaming into limbo. This must be done in strict child-to-parent | ||
241 | 147 | order. | ||
242 | 148 | |||
243 | 149 | If inventory_delta is None, no inventory delta generation is performed. | ||
244 | 150 | """ | ||
245 | 151 | tree_paths = sorted(viewitems(self._tree_path_ids), reverse=True) | ||
246 | 152 | with ui.ui_factory.nested_progress_bar() as child_pb: | ||
247 | 153 | for num, (path, trans_id) in enumerate(tree_paths): | ||
248 | 154 | # do not attempt to move root into a subdirectory of itself. | ||
249 | 155 | if path == '': | ||
250 | 156 | continue | ||
251 | 157 | child_pb.update(gettext('removing file'), num, len(tree_paths)) | ||
252 | 158 | full_path = self._tree.abspath(path) | ||
253 | 159 | if trans_id in self._removed_contents: | ||
254 | 160 | delete_path = os.path.join(self._deletiondir, trans_id) | ||
255 | 161 | mover.pre_delete(full_path, delete_path) | ||
256 | 162 | elif (trans_id in self._new_name or | ||
257 | 163 | trans_id in self._new_parent): | ||
258 | 164 | try: | ||
259 | 165 | mover.rename(full_path, self._limbo_name(trans_id)) | ||
260 | 166 | except TransformRenameFailed as e: | ||
261 | 167 | if e.errno != errno.ENOENT: | ||
262 | 168 | raise | ||
263 | 169 | else: | ||
264 | 170 | self.rename_count += 1 | ||
265 | 171 | |||
266 | 172 | def _apply_insertions(self, mover): | ||
267 | 173 | """Perform tree operations that insert directory/inventory names. | ||
268 | 174 | |||
269 | 175 | That is, create any files that need to be created, and restore from | ||
270 | 176 | limbo any files that needed renaming. This must be done in strict | ||
271 | 177 | parent-to-child order. | ||
272 | 178 | |||
273 | 179 | If inventory_delta is None, no inventory delta is calculated, and | ||
274 | 180 | no list of modified paths is returned. | ||
275 | 181 | """ | ||
276 | 182 | new_paths = self.new_paths(filesystem_only=True) | ||
277 | 183 | modified_paths = [] | ||
278 | 184 | with ui.ui_factory.nested_progress_bar() as child_pb: | ||
279 | 185 | for num, (path, trans_id) in enumerate(new_paths): | ||
280 | 186 | if (num % 10) == 0: | ||
281 | 187 | child_pb.update(gettext('adding file'), | ||
282 | 188 | num, len(new_paths)) | ||
283 | 189 | full_path = self._tree.abspath(path) | ||
284 | 190 | if trans_id in self._needs_rename: | ||
285 | 191 | try: | ||
286 | 192 | mover.rename(self._limbo_name(trans_id), full_path) | ||
287 | 193 | except TransformRenameFailed as e: | ||
288 | 194 | # We may be renaming a dangling inventory id | ||
289 | 195 | if e.errno != errno.ENOENT: | ||
290 | 196 | raise | ||
291 | 197 | else: | ||
292 | 198 | self.rename_count += 1 | ||
293 | 199 | # TODO: if trans_id in self._observed_sha1s, we should | ||
294 | 200 | # re-stat the final target, since ctime will be | ||
295 | 201 | # updated by the change. | ||
296 | 202 | if (trans_id in self._new_contents | ||
297 | 203 | or self.path_changed(trans_id)): | ||
298 | 204 | if trans_id in self._new_contents: | ||
299 | 205 | modified_paths.append(full_path) | ||
300 | 206 | if trans_id in self._new_executability: | ||
301 | 207 | self._set_executability(path, trans_id) | ||
302 | 208 | if trans_id in self._observed_sha1s: | ||
303 | 209 | o_sha1, o_st_val = self._observed_sha1s[trans_id] | ||
304 | 210 | st = osutils.lstat(full_path) | ||
305 | 211 | self._observed_sha1s[trans_id] = (o_sha1, st) | ||
306 | 212 | for path, trans_id in new_paths: | ||
307 | 213 | # new_paths includes stuff like workingtree conflicts. Only the | ||
308 | 214 | # stuff in new_contents actually comes from limbo. | ||
309 | 215 | if trans_id in self._limbo_files: | ||
310 | 216 | del self._limbo_files[trans_id] | ||
311 | 217 | self._new_contents.clear() | ||
312 | 218 | return modified_paths | ||
313 | 219 | |||
314 | 220 | def _apply_observed_sha1s(self): | ||
315 | 221 | """After we have finished renaming everything, update observed sha1s | ||
316 | 222 | |||
317 | 223 | This has to be done after self._tree.apply_inventory_delta, otherwise | ||
318 | 224 | it doesn't know anything about the files we are updating. Also, we want | ||
319 | 225 | to do this as late as possible, so that most entries end up cached. | ||
320 | 226 | """ | ||
321 | 227 | # TODO: this doesn't update the stat information for directories. So | ||
322 | 228 | # the first 'bzr status' will still need to rewrite | ||
323 | 229 | # .bzr/checkout/dirstate. However, we at least don't need to | ||
324 | 230 | # re-read all of the files. | ||
325 | 231 | # TODO: If the operation took a while, we could do a time.sleep(3) here | ||
326 | 232 | # to allow the clock to tick over and ensure we won't have any | ||
327 | 233 | # problems. (we could observe start time, and finish time, and if | ||
328 | 234 | # it is less than eg 10% overhead, add a sleep call.) | ||
329 | 235 | paths = FinalPaths(self) | ||
330 | 236 | for trans_id, observed in viewitems(self._observed_sha1s): | ||
331 | 237 | path = paths.get_path(trans_id) | ||
332 | 238 | self._tree._observed_sha1(path, observed) | ||
333 | 239 | |||
334 | 240 | def get_preview_tree(self): | ||
335 | 241 | """Return a tree representing the result of the transform. | ||
336 | 242 | |||
337 | 243 | The tree is a snapshot, and altering the TreeTransform will invalidate | ||
338 | 244 | it. | ||
339 | 245 | """ | ||
340 | 246 | return _PreviewTree(self) | ||
341 | 247 | |||
342 | 248 | def _inventory_altered(self): | ||
343 | 249 | """Determine which trans_ids need new Inventory entries. | ||
344 | 250 | |||
345 | 251 | An new entry is needed when anything that would be reflected by an | ||
346 | 252 | inventory entry changes, including file name, file_id, parent file_id, | ||
347 | 253 | file kind, and the execute bit. | ||
348 | 254 | |||
349 | 255 | Some care is taken to return entries with real changes, not cases | ||
350 | 256 | where the value is deleted and then restored to its original value, | ||
351 | 257 | but some actually unchanged values may be returned. | ||
352 | 258 | |||
353 | 259 | :returns: A list of (path, trans_id) for all items requiring an | ||
354 | 260 | inventory change. Ordered by path. | ||
355 | 261 | """ | ||
356 | 262 | changed_ids = set() | ||
357 | 263 | # Find entries whose file_ids are new (or changed). | ||
358 | 264 | new_file_id = set(t for t in self._new_id | ||
359 | 265 | if self._new_id[t] != self.tree_file_id(t)) | ||
360 | 266 | for id_set in [self._new_name, self._new_parent, new_file_id, | ||
361 | 267 | self._new_executability]: | ||
362 | 268 | changed_ids.update(id_set) | ||
363 | 269 | # removing implies a kind change | ||
364 | 270 | changed_kind = set(self._removed_contents) | ||
365 | 271 | # so does adding | ||
366 | 272 | changed_kind.intersection_update(self._new_contents) | ||
367 | 273 | # Ignore entries that are already known to have changed. | ||
368 | 274 | changed_kind.difference_update(changed_ids) | ||
369 | 275 | # to keep only the truly changed ones | ||
370 | 276 | changed_kind = (t for t in changed_kind | ||
371 | 277 | if self.tree_kind(t) != self.final_kind(t)) | ||
372 | 278 | # all kind changes will alter the inventory | ||
373 | 279 | changed_ids.update(changed_kind) | ||
374 | 280 | # To find entries with changed parent_ids, find parents which existed, | ||
375 | 281 | # but changed file_id. | ||
376 | 282 | # Now add all their children to the set. | ||
377 | 283 | for parent_trans_id in new_file_id: | ||
378 | 284 | changed_ids.update(self.iter_tree_children(parent_trans_id)) | ||
379 | 285 | return sorted(FinalPaths(self).get_paths(changed_ids)) | ||
380 | 286 | |||
381 | 287 | def _generate_inventory_delta(self): | ||
382 | 288 | """Generate an inventory delta for the current transform.""" | ||
383 | 289 | inventory_delta = [] | ||
384 | 290 | new_paths = self._inventory_altered() | ||
385 | 291 | total_entries = len(new_paths) + len(self._removed_id) | ||
386 | 292 | with ui.ui_factory.nested_progress_bar() as child_pb: | ||
387 | 293 | for num, trans_id in enumerate(self._removed_id): | ||
388 | 294 | if (num % 10) == 0: | ||
389 | 295 | child_pb.update(gettext('removing file'), | ||
390 | 296 | num, total_entries) | ||
391 | 297 | if trans_id == self._new_root: | ||
392 | 298 | file_id = self._tree.path2id('') | ||
393 | 299 | else: | ||
394 | 300 | file_id = self.tree_file_id(trans_id) | ||
395 | 301 | # File-id isn't really being deleted, just moved | ||
396 | 302 | if file_id in self._r_new_id: | ||
397 | 303 | continue | ||
398 | 304 | path = self._tree_id_paths[trans_id] | ||
399 | 305 | inventory_delta.append((path, None, file_id, None)) | ||
400 | 306 | new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in | ||
401 | 307 | new_paths) | ||
402 | 308 | for num, (path, trans_id) in enumerate(new_paths): | ||
403 | 309 | if (num % 10) == 0: | ||
404 | 310 | child_pb.update(gettext('adding file'), | ||
405 | 311 | num + len(self._removed_id), total_entries) | ||
406 | 312 | file_id = new_path_file_ids[trans_id] | ||
407 | 313 | if file_id is None: | ||
408 | 314 | continue | ||
409 | 315 | kind = self.final_kind(trans_id) | ||
410 | 316 | if kind is None: | ||
411 | 317 | kind = self._tree.stored_kind(self._tree.id2path(file_id)) | ||
412 | 318 | parent_trans_id = self.final_parent(trans_id) | ||
413 | 319 | parent_file_id = new_path_file_ids.get(parent_trans_id) | ||
414 | 320 | if parent_file_id is None: | ||
415 | 321 | parent_file_id = self.final_file_id(parent_trans_id) | ||
416 | 322 | if trans_id in self._new_reference_revision: | ||
417 | 323 | new_entry = inventory.TreeReference( | ||
418 | 324 | file_id, | ||
419 | 325 | self._new_name[trans_id], | ||
420 | 326 | self.final_file_id(self._new_parent[trans_id]), | ||
421 | 327 | None, self._new_reference_revision[trans_id]) | ||
422 | 328 | else: | ||
423 | 329 | new_entry = inventory.make_entry(kind, | ||
424 | 330 | self.final_name(trans_id), | ||
425 | 331 | parent_file_id, file_id) | ||
426 | 332 | try: | ||
427 | 333 | old_path = self._tree.id2path(new_entry.file_id) | ||
428 | 334 | except errors.NoSuchId: | ||
429 | 335 | old_path = None | ||
430 | 336 | new_executability = self._new_executability.get(trans_id) | ||
431 | 337 | if new_executability is not None: | ||
432 | 338 | new_entry.executable = new_executability | ||
433 | 339 | inventory_delta.append( | ||
434 | 340 | (old_path, path, new_entry.file_id, new_entry)) | ||
435 | 341 | return inventory_delta | ||
436 | 342 | |||
437 | 343 | |||
438 | 344 | class TransformPreview(InventoryTreeTransform): | ||
439 | 345 | """A TreeTransform for generating preview trees. | ||
440 | 346 | |||
441 | 347 | Unlike TreeTransform, this version works when the input tree is a | ||
442 | 348 | RevisionTree, rather than a WorkingTree. As a result, it tends to ignore | ||
443 | 349 | unversioned files in the input tree. | ||
444 | 350 | """ | ||
445 | 351 | |||
446 | 352 | def __init__(self, tree, pb=None, case_sensitive=True): | ||
447 | 353 | tree.lock_read() | ||
448 | 354 | limbodir = osutils.mkdtemp(prefix='bzr-limbo-') | ||
449 | 355 | DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive) | ||
450 | 356 | |||
451 | 357 | def canonical_path(self, path): | ||
452 | 358 | return path | ||
453 | 359 | |||
454 | 360 | def tree_kind(self, trans_id): | ||
455 | 361 | path = self._tree_id_paths.get(trans_id) | ||
456 | 362 | if path is None: | ||
457 | 363 | return None | ||
458 | 364 | kind = self._tree.path_content_summary(path)[0] | ||
459 | 365 | if kind == 'missing': | ||
460 | 366 | kind = None | ||
461 | 367 | return kind | ||
462 | 368 | |||
463 | 369 | def _set_mode(self, trans_id, mode_id, typefunc): | ||
464 | 370 | """Set the mode of new file contents. | ||
465 | 371 | The mode_id is the existing file to get the mode from (often the same | ||
466 | 372 | as trans_id). The operation is only performed if there's a mode match | ||
467 | 373 | according to typefunc. | ||
468 | 374 | """ | ||
469 | 375 | # is it ok to ignore this? probably | ||
470 | 376 | pass | ||
471 | 377 | |||
472 | 378 | def iter_tree_children(self, parent_id): | ||
473 | 379 | """Iterate through the entry's tree children, if any""" | ||
474 | 380 | try: | ||
475 | 381 | path = self._tree_id_paths[parent_id] | ||
476 | 382 | except KeyError: | ||
477 | 383 | return | ||
478 | 384 | try: | ||
479 | 385 | entry = next(self._tree.iter_entries_by_dir( | ||
480 | 386 | specific_files=[path]))[1] | ||
481 | 387 | except StopIteration: | ||
482 | 388 | return | ||
483 | 389 | children = getattr(entry, 'children', {}) | ||
484 | 390 | for child in children: | ||
485 | 391 | childpath = joinpath(path, child) | ||
486 | 392 | yield self.trans_id_tree_path(childpath) | ||
487 | 393 | |||
488 | 394 | def new_orphan(self, trans_id, parent_id): | ||
489 | 395 | raise NotImplementedError(self.new_orphan) | ||
490 | 396 | |||
491 | 397 | |||
492 | 398 | class _PreviewTree(inventorytree.InventoryTree): | ||
493 | 399 | """Partial implementation of Tree to support show_diff_trees""" | ||
494 | 400 | |||
495 | 401 | def __init__(self, transform): | ||
496 | 402 | self._transform = transform | ||
497 | 403 | self._final_paths = FinalPaths(transform) | ||
498 | 404 | self.__by_parent = None | ||
499 | 405 | self._parent_ids = [] | ||
500 | 406 | self._all_children_cache = {} | ||
501 | 407 | self._path2trans_id_cache = {} | ||
502 | 408 | self._final_name_cache = {} | ||
503 | 409 | self._iter_changes_cache = { | ||
504 | 410 | c.file_id: c for c in self._transform.iter_changes()} | ||
505 | 411 | |||
506 | 412 | def supports_tree_reference(self): | ||
507 | 413 | # TODO(jelmer): Support tree references in _PreviewTree. | ||
508 | 414 | # return self._transform._tree.supports_tree_reference() | ||
509 | 415 | return False | ||
510 | 416 | |||
511 | 417 | def _content_change(self, file_id): | ||
512 | 418 | """Return True if the content of this file changed""" | ||
513 | 419 | changes = self._iter_changes_cache.get(file_id) | ||
514 | 420 | return (changes is not None and changes.changed_content) | ||
515 | 421 | |||
516 | 422 | def _get_repository(self): | ||
517 | 423 | repo = getattr(self._transform._tree, '_repository', None) | ||
518 | 424 | if repo is None: | ||
519 | 425 | repo = self._transform._tree.branch.repository | ||
520 | 426 | return repo | ||
521 | 427 | |||
522 | 428 | def _iter_parent_trees(self): | ||
523 | 429 | for revision_id in self.get_parent_ids(): | ||
524 | 430 | try: | ||
525 | 431 | yield self.revision_tree(revision_id) | ||
526 | 432 | except errors.NoSuchRevisionInTree: | ||
527 | 433 | yield self._get_repository().revision_tree(revision_id) | ||
528 | 434 | |||
529 | 435 | def _get_file_revision(self, path, file_id, vf, tree_revision): | ||
530 | 436 | parent_keys = [ | ||
531 | 437 | (file_id, t.get_file_revision(t.id2path(file_id))) | ||
532 | 438 | for t in self._iter_parent_trees()] | ||
533 | 439 | vf.add_lines((file_id, tree_revision), parent_keys, | ||
534 | 440 | self.get_file_lines(path)) | ||
535 | 441 | repo = self._get_repository() | ||
536 | 442 | base_vf = repo.texts | ||
537 | 443 | if base_vf not in vf.fallback_versionedfiles: | ||
538 | 444 | vf.fallback_versionedfiles.append(base_vf) | ||
539 | 445 | return tree_revision | ||
540 | 446 | |||
541 | 447 | def _stat_limbo_file(self, trans_id): | ||
542 | 448 | name = self._transform._limbo_name(trans_id) | ||
543 | 449 | return os.lstat(name) | ||
544 | 450 | |||
545 | 451 | @property | ||
546 | 452 | def _by_parent(self): | ||
547 | 453 | if self.__by_parent is None: | ||
548 | 454 | self.__by_parent = self._transform.by_parent() | ||
549 | 455 | return self.__by_parent | ||
550 | 456 | |||
551 | 457 | def _comparison_data(self, entry, path): | ||
552 | 458 | kind, size, executable, link_or_sha1 = self.path_content_summary(path) | ||
553 | 459 | if kind == 'missing': | ||
554 | 460 | kind = None | ||
555 | 461 | executable = False | ||
556 | 462 | else: | ||
557 | 463 | file_id = self._transform.final_file_id(self._path2trans_id(path)) | ||
558 | 464 | executable = self.is_executable(path) | ||
559 | 465 | return kind, executable, None | ||
560 | 466 | |||
561 | 467 | def is_locked(self): | ||
562 | 468 | return False | ||
563 | 469 | |||
564 | 470 | def lock_read(self): | ||
565 | 471 | # Perhaps in theory, this should lock the TreeTransform? | ||
566 | 472 | return lock.LogicalLockResult(self.unlock) | ||
567 | 473 | |||
568 | 474 | def unlock(self): | ||
569 | 475 | pass | ||
570 | 476 | |||
571 | 477 | @property | ||
572 | 478 | def root_inventory(self): | ||
573 | 479 | """This Tree does not use inventory as its backing data.""" | ||
574 | 480 | raise NotImplementedError(_PreviewTree.root_inventory) | ||
575 | 481 | |||
576 | 482 | def all_file_ids(self): | ||
577 | 483 | tree_ids = set(self._transform._tree.all_file_ids()) | ||
578 | 484 | tree_ids.difference_update(self._transform.tree_file_id(t) | ||
579 | 485 | for t in self._transform._removed_id) | ||
580 | 486 | tree_ids.update(viewvalues(self._transform._new_id)) | ||
581 | 487 | return tree_ids | ||
582 | 488 | |||
583 | 489 | def all_versioned_paths(self): | ||
584 | 490 | tree_paths = set(self._transform._tree.all_versioned_paths()) | ||
585 | 491 | |||
586 | 492 | tree_paths.difference_update( | ||
587 | 493 | self._transform.trans_id_tree_path(t) | ||
588 | 494 | for t in self._transform._removed_id) | ||
589 | 495 | |||
590 | 496 | tree_paths.update( | ||
591 | 497 | self._final_paths._determine_path(t) | ||
592 | 498 | for t in self._transform._new_id) | ||
593 | 499 | |||
594 | 500 | return tree_paths | ||
595 | 501 | |||
596 | 502 | def _path2trans_id(self, path): | ||
597 | 503 | # We must not use None here, because that is a valid value to store. | ||
598 | 504 | trans_id = self._path2trans_id_cache.get(path, object) | ||
599 | 505 | if trans_id is not object: | ||
600 | 506 | return trans_id | ||
601 | 507 | segments = osutils.splitpath(path) | ||
602 | 508 | cur_parent = self._transform.root | ||
603 | 509 | for cur_segment in segments: | ||
604 | 510 | for child in self._all_children(cur_parent): | ||
605 | 511 | final_name = self._final_name_cache.get(child) | ||
606 | 512 | if final_name is None: | ||
607 | 513 | final_name = self._transform.final_name(child) | ||
608 | 514 | self._final_name_cache[child] = final_name | ||
609 | 515 | if final_name == cur_segment: | ||
610 | 516 | cur_parent = child | ||
611 | 517 | break | ||
612 | 518 | else: | ||
613 | 519 | self._path2trans_id_cache[path] = None | ||
614 | 520 | return None | ||
615 | 521 | self._path2trans_id_cache[path] = cur_parent | ||
616 | 522 | return cur_parent | ||
617 | 523 | |||
618 | 524 | def path2id(self, path): | ||
619 | 525 | if isinstance(path, list): | ||
620 | 526 | if path == []: | ||
621 | 527 | path = [""] | ||
622 | 528 | path = osutils.pathjoin(*path) | ||
623 | 529 | return self._transform.final_file_id(self._path2trans_id(path)) | ||
624 | 530 | |||
625 | 531 | def id2path(self, file_id, recurse='down'): | ||
626 | 532 | trans_id = self._transform.trans_id_file_id(file_id) | ||
627 | 533 | try: | ||
628 | 534 | return self._final_paths._determine_path(trans_id) | ||
629 | 535 | except NoFinalPath: | ||
630 | 536 | raise errors.NoSuchId(self, file_id) | ||
631 | 537 | |||
632 | 538 | def _all_children(self, trans_id): | ||
633 | 539 | children = self._all_children_cache.get(trans_id) | ||
634 | 540 | if children is not None: | ||
635 | 541 | return children | ||
636 | 542 | children = set(self._transform.iter_tree_children(trans_id)) | ||
637 | 543 | # children in the _new_parent set are provided by _by_parent. | ||
638 | 544 | children.difference_update(self._transform._new_parent) | ||
639 | 545 | children.update(self._by_parent.get(trans_id, [])) | ||
640 | 546 | self._all_children_cache[trans_id] = children | ||
641 | 547 | return children | ||
642 | 548 | |||
643 | 549 | def extras(self): | ||
644 | 550 | possible_extras = set(self._transform.trans_id_tree_path(p) for p | ||
645 | 551 | in self._transform._tree.extras()) | ||
646 | 552 | possible_extras.update(self._transform._new_contents) | ||
647 | 553 | possible_extras.update(self._transform._removed_id) | ||
648 | 554 | for trans_id in possible_extras: | ||
649 | 555 | if self._transform.final_file_id(trans_id) is None: | ||
650 | 556 | yield self._final_paths._determine_path(trans_id) | ||
651 | 557 | |||
652 | 558 | def _make_inv_entries(self, ordered_entries, specific_files=None): | ||
653 | 559 | for trans_id, parent_file_id in ordered_entries: | ||
654 | 560 | file_id = self._transform.final_file_id(trans_id) | ||
655 | 561 | if file_id is None: | ||
656 | 562 | continue | ||
657 | 563 | if (specific_files is not None | ||
658 | 564 | and self._final_paths.get_path(trans_id) not in specific_files): | ||
659 | 565 | continue | ||
660 | 566 | kind = self._transform.final_kind(trans_id) | ||
661 | 567 | if kind is None: | ||
662 | 568 | kind = self._transform._tree.stored_kind( | ||
663 | 569 | self._transform._tree.id2path(file_id)) | ||
664 | 570 | new_entry = inventory.make_entry( | ||
665 | 571 | kind, | ||
666 | 572 | self._transform.final_name(trans_id), | ||
667 | 573 | parent_file_id, file_id) | ||
668 | 574 | yield new_entry, trans_id | ||
669 | 575 | |||
670 | 576 | def _list_files_by_dir(self): | ||
671 | 577 | todo = [ROOT_PARENT] | ||
672 | 578 | ordered_ids = [] | ||
673 | 579 | while len(todo) > 0: | ||
674 | 580 | parent = todo.pop() | ||
675 | 581 | parent_file_id = self._transform.final_file_id(parent) | ||
676 | 582 | children = list(self._all_children(parent)) | ||
677 | 583 | paths = dict(zip(children, self._final_paths.get_paths(children))) | ||
678 | 584 | children.sort(key=paths.get) | ||
679 | 585 | todo.extend(reversed(children)) | ||
680 | 586 | for trans_id in children: | ||
681 | 587 | ordered_ids.append((trans_id, parent_file_id)) | ||
682 | 588 | return ordered_ids | ||
683 | 589 | |||
684 | 590 | def iter_child_entries(self, path): | ||
685 | 591 | trans_id = self._path2trans_id(path) | ||
686 | 592 | if trans_id is None: | ||
687 | 593 | raise errors.NoSuchFile(path) | ||
688 | 594 | todo = [(child_trans_id, trans_id) for child_trans_id in | ||
689 | 595 | self._all_children(trans_id)] | ||
690 | 596 | for entry, trans_id in self._make_inv_entries(todo): | ||
691 | 597 | yield entry | ||
692 | 598 | |||
693 | 599 | def iter_entries_by_dir(self, specific_files=None, recurse_nested=False): | ||
694 | 600 | if recurse_nested: | ||
695 | 601 | raise NotImplementedError( | ||
696 | 602 | 'follow tree references not yet supported') | ||
697 | 603 | |||
698 | 604 | # This may not be a maximally efficient implementation, but it is | ||
699 | 605 | # reasonably straightforward. An implementation that grafts the | ||
700 | 606 | # TreeTransform changes onto the tree's iter_entries_by_dir results | ||
701 | 607 | # might be more efficient, but requires tricky inferences about stack | ||
702 | 608 | # position. | ||
703 | 609 | ordered_ids = self._list_files_by_dir() | ||
704 | 610 | for entry, trans_id in self._make_inv_entries(ordered_ids, | ||
705 | 611 | specific_files): | ||
706 | 612 | yield self._final_paths.get_path(trans_id), entry | ||
707 | 613 | |||
708 | 614 | def _iter_entries_for_dir(self, dir_path): | ||
709 | 615 | """Return path, entry for items in a directory without recursing down.""" | ||
710 | 616 | ordered_ids = [] | ||
711 | 617 | dir_trans_id = self._path2trans_id(dir_path) | ||
712 | 618 | dir_id = self._transform.final_file_id(dir_trans_id) | ||
713 | 619 | for child_trans_id in self._all_children(dir_trans_id): | ||
714 | 620 | ordered_ids.append((child_trans_id, dir_id)) | ||
715 | 621 | path_entries = [] | ||
716 | 622 | for entry, trans_id in self._make_inv_entries(ordered_ids): | ||
717 | 623 | path_entries.append((self._final_paths.get_path(trans_id), entry)) | ||
718 | 624 | path_entries.sort() | ||
719 | 625 | return path_entries | ||
720 | 626 | |||
721 | 627 | def list_files(self, include_root=False, from_dir=None, recursive=True, | ||
722 | 628 | recurse_nested=False): | ||
723 | 629 | """See WorkingTree.list_files.""" | ||
724 | 630 | if recurse_nested: | ||
725 | 631 | raise NotImplementedError( | ||
726 | 632 | 'follow tree references not yet supported') | ||
727 | 633 | |||
728 | 634 | # XXX This should behave like WorkingTree.list_files, but is really | ||
729 | 635 | # more like RevisionTree.list_files. | ||
730 | 636 | if from_dir == '.': | ||
731 | 637 | from_dir = None | ||
732 | 638 | if recursive: | ||
733 | 639 | prefix = None | ||
734 | 640 | if from_dir: | ||
735 | 641 | prefix = from_dir + '/' | ||
736 | 642 | entries = self.iter_entries_by_dir() | ||
737 | 643 | for path, entry in entries: | ||
738 | 644 | if entry.name == '' and not include_root: | ||
739 | 645 | continue | ||
740 | 646 | if prefix: | ||
741 | 647 | if not path.startswith(prefix): | ||
742 | 648 | continue | ||
743 | 649 | path = path[len(prefix):] | ||
744 | 650 | yield path, 'V', entry.kind, entry | ||
745 | 651 | else: | ||
746 | 652 | if from_dir is None and include_root is True: | ||
747 | 653 | root_entry = inventory.make_entry( | ||
748 | 654 | 'directory', '', ROOT_PARENT, self.path2id('')) | ||
749 | 655 | yield '', 'V', 'directory', root_entry | ||
750 | 656 | entries = self._iter_entries_for_dir(from_dir or '') | ||
751 | 657 | for path, entry in entries: | ||
752 | 658 | yield path, 'V', entry.kind, entry | ||
753 | 659 | |||
754 | 660 | def kind(self, path): | ||
755 | 661 | trans_id = self._path2trans_id(path) | ||
756 | 662 | if trans_id is None: | ||
757 | 663 | raise errors.NoSuchFile(path) | ||
758 | 664 | return self._transform.final_kind(trans_id) | ||
759 | 665 | |||
760 | 666 | def stored_kind(self, path): | ||
761 | 667 | trans_id = self._path2trans_id(path) | ||
762 | 668 | if trans_id is None: | ||
763 | 669 | raise errors.NoSuchFile(path) | ||
764 | 670 | try: | ||
765 | 671 | return self._transform._new_contents[trans_id] | ||
766 | 672 | except KeyError: | ||
767 | 673 | return self._transform._tree.stored_kind(path) | ||
768 | 674 | |||
769 | 675 | def get_file_mtime(self, path): | ||
770 | 676 | """See Tree.get_file_mtime""" | ||
771 | 677 | file_id = self.path2id(path) | ||
772 | 678 | if file_id is None: | ||
773 | 679 | raise errors.NoSuchFile(path) | ||
774 | 680 | if not self._content_change(file_id): | ||
775 | 681 | return self._transform._tree.get_file_mtime( | ||
776 | 682 | self._transform._tree.id2path(file_id)) | ||
777 | 683 | trans_id = self._path2trans_id(path) | ||
778 | 684 | return self._stat_limbo_file(trans_id).st_mtime | ||
779 | 685 | |||
780 | 686 | def get_file_size(self, path): | ||
781 | 687 | """See Tree.get_file_size""" | ||
782 | 688 | trans_id = self._path2trans_id(path) | ||
783 | 689 | if trans_id is None: | ||
784 | 690 | raise errors.NoSuchFile(path) | ||
785 | 691 | kind = self._transform.final_kind(trans_id) | ||
786 | 692 | if kind != 'file': | ||
787 | 693 | return None | ||
788 | 694 | if trans_id in self._transform._new_contents: | ||
789 | 695 | return self._stat_limbo_file(trans_id).st_size | ||
790 | 696 | if self.kind(path) == 'file': | ||
791 | 697 | return self._transform._tree.get_file_size(path) | ||
792 | 698 | else: | ||
793 | 699 | return None | ||
794 | 700 | |||
795 | 701 | def get_file_verifier(self, path, stat_value=None): | ||
796 | 702 | trans_id = self._path2trans_id(path) | ||
797 | 703 | if trans_id is None: | ||
798 | 704 | raise errors.NoSuchFile(path) | ||
799 | 705 | kind = self._transform._new_contents.get(trans_id) | ||
800 | 706 | if kind is None: | ||
801 | 707 | return self._transform._tree.get_file_verifier(path) | ||
802 | 708 | if kind == 'file': | ||
803 | 709 | with self.get_file(path) as fileobj: | ||
804 | 710 | return ("SHA1", osutils.sha_file(fileobj)) | ||
805 | 711 | |||
806 | 712 | def get_file_sha1(self, path, stat_value=None): | ||
807 | 713 | trans_id = self._path2trans_id(path) | ||
808 | 714 | if trans_id is None: | ||
809 | 715 | raise errors.NoSuchFile(path) | ||
810 | 716 | kind = self._transform._new_contents.get(trans_id) | ||
811 | 717 | if kind is None: | ||
812 | 718 | return self._transform._tree.get_file_sha1(path) | ||
813 | 719 | if kind == 'file': | ||
814 | 720 | with self.get_file(path) as fileobj: | ||
815 | 721 | return osutils.sha_file(fileobj) | ||
816 | 722 | |||
817 | 723 | def get_reference_revision(self, path): | ||
818 | 724 | trans_id = self._path2trans_id(path) | ||
819 | 725 | if trans_id is None: | ||
820 | 726 | raise errors.NoSuchFile(path) | ||
821 | 727 | reference_revision = self._transform._new_reference_revision.get(trans_id) | ||
822 | 728 | if reference_revision is None: | ||
823 | 729 | return self._transform._tree.get_reference_revision(path) | ||
824 | 730 | return reference_revision | ||
825 | 731 | |||
826 | 732 | def is_executable(self, path): | ||
827 | 733 | trans_id = self._path2trans_id(path) | ||
828 | 734 | if trans_id is None: | ||
829 | 735 | return False | ||
830 | 736 | try: | ||
831 | 737 | return self._transform._new_executability[trans_id] | ||
832 | 738 | except KeyError: | ||
833 | 739 | try: | ||
834 | 740 | return self._transform._tree.is_executable(path) | ||
835 | 741 | except OSError as e: | ||
836 | 742 | if e.errno == errno.ENOENT: | ||
837 | 743 | return False | ||
838 | 744 | raise | ||
839 | 745 | except errors.NoSuchFile: | ||
840 | 746 | return False | ||
841 | 747 | |||
842 | 748 | def has_filename(self, path): | ||
843 | 749 | trans_id = self._path2trans_id(path) | ||
844 | 750 | if trans_id in self._transform._new_contents: | ||
845 | 751 | return True | ||
846 | 752 | elif trans_id in self._transform._removed_contents: | ||
847 | 753 | return False | ||
848 | 754 | else: | ||
849 | 755 | return self._transform._tree.has_filename(path) | ||
850 | 756 | |||
851 | 757 | def path_content_summary(self, path): | ||
852 | 758 | trans_id = self._path2trans_id(path) | ||
853 | 759 | tt = self._transform | ||
854 | 760 | tree_path = tt._tree_id_paths.get(trans_id) | ||
855 | 761 | kind = tt._new_contents.get(trans_id) | ||
856 | 762 | if kind is None: | ||
857 | 763 | if tree_path is None or trans_id in tt._removed_contents: | ||
858 | 764 | return 'missing', None, None, None | ||
859 | 765 | summary = tt._tree.path_content_summary(tree_path) | ||
860 | 766 | kind, size, executable, link_or_sha1 = summary | ||
861 | 767 | else: | ||
862 | 768 | link_or_sha1 = None | ||
863 | 769 | limbo_name = tt._limbo_name(trans_id) | ||
864 | 770 | if trans_id in tt._new_reference_revision: | ||
865 | 771 | kind = 'tree-reference' | ||
866 | 772 | if kind == 'file': | ||
867 | 773 | statval = os.lstat(limbo_name) | ||
868 | 774 | size = statval.st_size | ||
869 | 775 | if not tt._limbo_supports_executable(): | ||
870 | 776 | executable = False | ||
871 | 777 | else: | ||
872 | 778 | executable = statval.st_mode & S_IEXEC | ||
873 | 779 | else: | ||
874 | 780 | size = None | ||
875 | 781 | executable = None | ||
876 | 782 | if kind == 'symlink': | ||
877 | 783 | link_or_sha1 = os.readlink(limbo_name) | ||
878 | 784 | if not isinstance(link_or_sha1, text_type): | ||
879 | 785 | link_or_sha1 = link_or_sha1.decode(osutils._fs_enc) | ||
880 | 786 | executable = tt._new_executability.get(trans_id, executable) | ||
881 | 787 | return kind, size, executable, link_or_sha1 | ||
882 | 788 | |||
883 | 789 | def iter_changes(self, from_tree, include_unchanged=False, | ||
884 | 790 | specific_files=None, pb=None, extra_trees=None, | ||
885 | 791 | require_versioned=True, want_unversioned=False): | ||
886 | 792 | """See InterTree.iter_changes. | ||
887 | 793 | |||
888 | 794 | This has a fast path that is only used when the from_tree matches | ||
889 | 795 | the transform tree, and no fancy options are supplied. | ||
890 | 796 | """ | ||
891 | 797 | if (from_tree is not self._transform._tree or include_unchanged | ||
892 | 798 | or specific_files or want_unversioned): | ||
893 | 799 | return tree.InterTree.get(from_tree, self).iter_changes( | ||
894 | 800 | include_unchanged=include_unchanged, | ||
895 | 801 | specific_files=specific_files, | ||
896 | 802 | pb=pb, | ||
897 | 803 | extra_trees=extra_trees, | ||
898 | 804 | require_versioned=require_versioned, | ||
899 | 805 | want_unversioned=want_unversioned) | ||
900 | 806 | if want_unversioned: | ||
901 | 807 | raise ValueError('want_unversioned is not supported') | ||
902 | 808 | return self._transform.iter_changes() | ||
903 | 809 | |||
904 | 810 | def get_file(self, path): | ||
905 | 811 | """See Tree.get_file""" | ||
906 | 812 | file_id = self.path2id(path) | ||
907 | 813 | if not self._content_change(file_id): | ||
908 | 814 | return self._transform._tree.get_file(path) | ||
909 | 815 | trans_id = self._path2trans_id(path) | ||
910 | 816 | name = self._transform._limbo_name(trans_id) | ||
911 | 817 | return open(name, 'rb') | ||
912 | 818 | |||
913 | 819 | def get_file_with_stat(self, path): | ||
914 | 820 | return self.get_file(path), None | ||
915 | 821 | |||
916 | 822 | def annotate_iter(self, path, | ||
917 | 823 | default_revision=_mod_revision.CURRENT_REVISION): | ||
918 | 824 | file_id = self.path2id(path) | ||
919 | 825 | changes = self._iter_changes_cache.get(file_id) | ||
920 | 826 | if changes is None: | ||
921 | 827 | get_old = True | ||
922 | 828 | else: | ||
923 | 829 | changed_content, versioned, kind = ( | ||
924 | 830 | changes.changed_content, changes.versioned, changes.kind) | ||
925 | 831 | if kind[1] is None: | ||
926 | 832 | return None | ||
927 | 833 | get_old = (kind[0] == 'file' and versioned[0]) | ||
928 | 834 | if get_old: | ||
929 | 835 | old_annotation = self._transform._tree.annotate_iter( | ||
930 | 836 | path, default_revision=default_revision) | ||
931 | 837 | else: | ||
932 | 838 | old_annotation = [] | ||
933 | 839 | if changes is None: | ||
934 | 840 | return old_annotation | ||
935 | 841 | if not changed_content: | ||
936 | 842 | return old_annotation | ||
937 | 843 | # TODO: This is doing something similar to what WT.annotate_iter is | ||
938 | 844 | # doing, however it fails slightly because it doesn't know what | ||
939 | 845 | # the *other* revision_id is, so it doesn't know how to give the | ||
940 | 846 | # other as the origin for some lines, they all get | ||
941 | 847 | # 'default_revision' | ||
942 | 848 | # It would be nice to be able to use the new Annotator based | ||
943 | 849 | # approach, as well. | ||
944 | 850 | return annotate.reannotate([old_annotation], | ||
945 | 851 | self.get_file(path).readlines(), | ||
946 | 852 | default_revision) | ||
947 | 853 | |||
948 | 854 | def get_symlink_target(self, path): | ||
949 | 855 | """See Tree.get_symlink_target""" | ||
950 | 856 | file_id = self.path2id(path) | ||
951 | 857 | if not self._content_change(file_id): | ||
952 | 858 | return self._transform._tree.get_symlink_target(path) | ||
953 | 859 | trans_id = self._path2trans_id(path) | ||
954 | 860 | name = self._transform._limbo_name(trans_id) | ||
955 | 861 | return osutils.readlink(name) | ||
956 | 862 | |||
957 | 863 | def walkdirs(self, prefix=''): | ||
958 | 864 | pending = [self._transform.root] | ||
959 | 865 | while len(pending) > 0: | ||
960 | 866 | parent_id = pending.pop() | ||
961 | 867 | children = [] | ||
962 | 868 | subdirs = [] | ||
963 | 869 | prefix = prefix.rstrip('/') | ||
964 | 870 | parent_path = self._final_paths.get_path(parent_id) | ||
965 | 871 | parent_file_id = self._transform.final_file_id(parent_id) | ||
966 | 872 | for child_id in self._all_children(parent_id): | ||
967 | 873 | path_from_root = self._final_paths.get_path(child_id) | ||
968 | 874 | basename = self._transform.final_name(child_id) | ||
969 | 875 | file_id = self._transform.final_file_id(child_id) | ||
970 | 876 | kind = self._transform.final_kind(child_id) | ||
971 | 877 | if kind is not None: | ||
972 | 878 | versioned_kind = kind | ||
973 | 879 | else: | ||
974 | 880 | kind = 'unknown' | ||
975 | 881 | versioned_kind = self._transform._tree.stored_kind( | ||
976 | 882 | self._transform._tree.id2path(file_id)) | ||
977 | 883 | if versioned_kind == 'directory': | ||
978 | 884 | subdirs.append(child_id) | ||
979 | 885 | children.append((path_from_root, basename, kind, None, | ||
980 | 886 | file_id, versioned_kind)) | ||
981 | 887 | children.sort() | ||
982 | 888 | if parent_path.startswith(prefix): | ||
983 | 889 | yield (parent_path, parent_file_id), children | ||
984 | 890 | pending.extend(sorted(subdirs, key=self._final_paths.get_path, | ||
985 | 891 | reverse=True)) | ||
986 | 892 | |||
987 | 893 | def get_parent_ids(self): | ||
988 | 894 | return self._parent_ids | ||
989 | 895 | |||
990 | 896 | def set_parent_ids(self, parent_ids): | ||
991 | 897 | self._parent_ids = parent_ids | ||
992 | 898 | |||
993 | 899 | def get_revision_tree(self, revision_id): | ||
994 | 900 | return self._transform._tree.get_revision_tree(revision_id) | ||
995 | 901 | |||
996 | 902 | |||
997 | 903 | |||
998 | 0 | 904 | ||
999 | === modified file 'breezy/bzr/workingtree.py' | |||
1000 | --- breezy/bzr/workingtree.py 2020-07-04 00:38:37 +0000 | |||
1001 | +++ breezy/bzr/workingtree.py 2020-07-05 13:48:17 +0000 | |||
1002 | @@ -175,8 +175,8 @@ | |||
1003 | 175 | self.case_sensitive = False | 175 | self.case_sensitive = False |
1004 | 176 | 176 | ||
1005 | 177 | def transform(self, pb=None): | 177 | def transform(self, pb=None): |
1008 | 178 | from ..transform import TreeTransform | 178 | from .transform import InventoryTreeTransform |
1009 | 179 | return TreeTransform(self, pb=pb) | 179 | return InventoryTreeTransform(self, pb=pb) |
1010 | 180 | 180 | ||
1011 | 181 | def _setup_directory_is_tree_reference(self): | 181 | def _setup_directory_is_tree_reference(self): |
1012 | 182 | if self._branch.repository._format.supports_tree_reference: | 182 | if self._branch.repository._format.supports_tree_reference: |
1013 | 183 | 183 | ||
1014 | === modified file 'breezy/errors.py' | |||
1015 | --- breezy/errors.py 2020-06-25 21:17:44 +0000 | |||
1016 | +++ breezy/errors.py 2020-07-05 13:48:17 +0000 | |||
1017 | @@ -1508,22 +1508,6 @@ | |||
1018 | 1508 | _fmt = "Parameter %(param)s is neither unicode nor utf8." | 1508 | _fmt = "Parameter %(param)s is neither unicode nor utf8." |
1019 | 1509 | 1509 | ||
1020 | 1510 | 1510 | ||
1021 | 1511 | class CantMoveRoot(BzrError): | ||
1022 | 1512 | |||
1023 | 1513 | _fmt = "Moving the root directory is not supported at this time" | ||
1024 | 1514 | |||
1025 | 1515 | |||
1026 | 1516 | class TransformRenameFailed(BzrError): | ||
1027 | 1517 | |||
1028 | 1518 | _fmt = "Failed to rename %(from_path)s to %(to_path)s: %(why)s" | ||
1029 | 1519 | |||
1030 | 1520 | def __init__(self, from_path, to_path, why, errno): | ||
1031 | 1521 | self.from_path = from_path | ||
1032 | 1522 | self.to_path = to_path | ||
1033 | 1523 | self.why = why | ||
1034 | 1524 | self.errno = errno | ||
1035 | 1525 | |||
1036 | 1526 | |||
1037 | 1527 | class BzrMoveFailedError(BzrError): | 1511 | class BzrMoveFailedError(BzrError): |
1038 | 1528 | 1512 | ||
1039 | 1529 | _fmt = ("Could not move %(from_path)s%(operator)s %(to_path)s" | 1513 | _fmt = ("Could not move %(from_path)s%(operator)s %(to_path)s" |
1040 | 1530 | 1514 | ||
1041 | === added file 'breezy/git/transform.py' | |||
1042 | --- breezy/git/transform.py 1970-01-01 00:00:00 +0000 | |||
1043 | +++ breezy/git/transform.py 2020-07-05 13:48:17 +0000 | |||
1044 | @@ -0,0 +1,275 @@ | |||
1045 | 1 | # Copyright (C) 2006-2011 Canonical Ltd | ||
1046 | 2 | # Copyright (C) 2020 Breezy Developers | ||
1047 | 3 | # | ||
1048 | 4 | # This program is free software; you can redistribute it and/or modify | ||
1049 | 5 | # it under the terms of the GNU General Public License as published by | ||
1050 | 6 | # the Free Software Foundation; either version 2 of the License, or | ||
1051 | 7 | # (at your option) any later version. | ||
1052 | 8 | # | ||
1053 | 9 | # This program is distributed in the hope that it will be useful, | ||
1054 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1055 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1056 | 12 | # GNU General Public License for more details. | ||
1057 | 13 | # | ||
1058 | 14 | # You should have received a copy of the GNU General Public License | ||
1059 | 15 | # along with this program; if not, write to the Free Software | ||
1060 | 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
1061 | 17 | |||
1062 | 18 | from __future__ import absolute_import | ||
1063 | 19 | |||
1064 | 20 | import errno | ||
1065 | 21 | import os | ||
1066 | 22 | |||
1067 | 23 | from .. import errors, ui | ||
1068 | 24 | from ..i18n import gettext | ||
1069 | 25 | from ..mutabletree import MutableTree | ||
1070 | 26 | from ..sixish import viewitems | ||
1071 | 27 | from ..transform import ( | ||
1072 | 28 | TreeTransform, | ||
1073 | 29 | _TransformResults, | ||
1074 | 30 | _FileMover, | ||
1075 | 31 | FinalPaths, | ||
1076 | 32 | unique_add, | ||
1077 | 33 | TransformRenameFailed, | ||
1078 | 34 | ) | ||
1079 | 35 | |||
1080 | 36 | from ..bzr import inventory | ||
1081 | 37 | from ..bzr.transform import TransformPreview as GitTransformPreview | ||
1082 | 38 | |||
1083 | 39 | |||
1084 | 40 | class GitTreeTransform(TreeTransform): | ||
1085 | 41 | """Tree transform for Bazaar trees.""" | ||
1086 | 42 | |||
1087 | 43 | def version_file(self, trans_id, file_id=None): | ||
1088 | 44 | """Schedule a file to become versioned.""" | ||
1089 | 45 | if file_id is None: | ||
1090 | 46 | raise ValueError() | ||
1091 | 47 | unique_add(self._new_id, trans_id, file_id) | ||
1092 | 48 | unique_add(self._r_new_id, file_id, trans_id) | ||
1093 | 49 | |||
1094 | 50 | def cancel_versioning(self, trans_id): | ||
1095 | 51 | """Undo a previous versioning of a file""" | ||
1096 | 52 | file_id = self._new_id[trans_id] | ||
1097 | 53 | del self._new_id[trans_id] | ||
1098 | 54 | del self._r_new_id[file_id] | ||
1099 | 55 | |||
1100 | 56 | def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None): | ||
1101 | 57 | """Apply all changes to the inventory and filesystem. | ||
1102 | 58 | |||
1103 | 59 | If filesystem or inventory conflicts are present, MalformedTransform | ||
1104 | 60 | will be thrown. | ||
1105 | 61 | |||
1106 | 62 | If apply succeeds, finalize is not necessary. | ||
1107 | 63 | |||
1108 | 64 | :param no_conflicts: if True, the caller guarantees there are no | ||
1109 | 65 | conflicts, so no check is made. | ||
1110 | 66 | :param precomputed_delta: An inventory delta to use instead of | ||
1111 | 67 | calculating one. | ||
1112 | 68 | :param _mover: Supply an alternate FileMover, for testing | ||
1113 | 69 | """ | ||
1114 | 70 | for hook in MutableTree.hooks['pre_transform']: | ||
1115 | 71 | hook(self._tree, self) | ||
1116 | 72 | if not no_conflicts: | ||
1117 | 73 | self._check_malformed() | ||
1118 | 74 | with ui.ui_factory.nested_progress_bar() as child_pb: | ||
1119 | 75 | if precomputed_delta is None: | ||
1120 | 76 | child_pb.update(gettext('Apply phase'), 0, 2) | ||
1121 | 77 | changes = self._generate_transform_changes() | ||
1122 | 78 | offset = 1 | ||
1123 | 79 | else: | ||
1124 | 80 | changes = [ | ||
1125 | 81 | (op, np, ie) for (op, np, fid, ie) in precomputed_delta] | ||
1126 | 82 | offset = 0 | ||
1127 | 83 | if _mover is None: | ||
1128 | 84 | mover = _FileMover() | ||
1129 | 85 | else: | ||
1130 | 86 | mover = _mover | ||
1131 | 87 | try: | ||
1132 | 88 | child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset) | ||
1133 | 89 | self._apply_removals(mover) | ||
1134 | 90 | child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset) | ||
1135 | 91 | modified_paths = self._apply_insertions(mover) | ||
1136 | 92 | except BaseException: | ||
1137 | 93 | mover.rollback() | ||
1138 | 94 | raise | ||
1139 | 95 | else: | ||
1140 | 96 | mover.apply_deletions() | ||
1141 | 97 | if self.final_file_id(self.root) is None: | ||
1142 | 98 | changes = [e for e in changes if e[0] != ''] | ||
1143 | 99 | self._tree._apply_transform_delta(changes) | ||
1144 | 100 | self._done = True | ||
1145 | 101 | self.finalize() | ||
1146 | 102 | return _TransformResults(modified_paths, self.rename_count) | ||
1147 | 103 | |||
1148 | 104 | def _apply_removals(self, mover): | ||
1149 | 105 | """Perform tree operations that remove directory/inventory names. | ||
1150 | 106 | |||
1151 | 107 | That is, delete files that are to be deleted, and put any files that | ||
1152 | 108 | need renaming into limbo. This must be done in strict child-to-parent | ||
1153 | 109 | order. | ||
1154 | 110 | |||
1155 | 111 | If inventory_delta is None, no inventory delta generation is performed. | ||
1156 | 112 | """ | ||
1157 | 113 | tree_paths = sorted(viewitems(self._tree_path_ids), reverse=True) | ||
1158 | 114 | with ui.ui_factory.nested_progress_bar() as child_pb: | ||
1159 | 115 | for num, (path, trans_id) in enumerate(tree_paths): | ||
1160 | 116 | # do not attempt to move root into a subdirectory of itself. | ||
1161 | 117 | if path == '': | ||
1162 | 118 | continue | ||
1163 | 119 | child_pb.update(gettext('removing file'), num, len(tree_paths)) | ||
1164 | 120 | full_path = self._tree.abspath(path) | ||
1165 | 121 | if trans_id in self._removed_contents: | ||
1166 | 122 | delete_path = os.path.join(self._deletiondir, trans_id) | ||
1167 | 123 | mover.pre_delete(full_path, delete_path) | ||
1168 | 124 | elif (trans_id in self._new_name or | ||
1169 | 125 | trans_id in self._new_parent): | ||
1170 | 126 | try: | ||
1171 | 127 | mover.rename(full_path, self._limbo_name(trans_id)) | ||
1172 | 128 | except TransformRenameFailed as e: | ||
1173 | 129 | if e.errno != errno.ENOENT: | ||
1174 | 130 | raise | ||
1175 | 131 | else: | ||
1176 | 132 | self.rename_count += 1 | ||
1177 | 133 | |||
1178 | 134 | def _apply_insertions(self, mover): | ||
1179 | 135 | """Perform tree operations that insert directory/inventory names. | ||
1180 | 136 | |||
1181 | 137 | That is, create any files that need to be created, and restore from | ||
1182 | 138 | limbo any files that needed renaming. This must be done in strict | ||
1183 | 139 | parent-to-child order. | ||
1184 | 140 | |||
1185 | 141 | If inventory_delta is None, no inventory delta is calculated, and | ||
1186 | 142 | no list of modified paths is returned. | ||
1187 | 143 | """ | ||
1188 | 144 | new_paths = self.new_paths(filesystem_only=True) | ||
1189 | 145 | modified_paths = [] | ||
1190 | 146 | with ui.ui_factory.nested_progress_bar() as child_pb: | ||
1191 | 147 | for num, (path, trans_id) in enumerate(new_paths): | ||
1192 | 148 | if (num % 10) == 0: | ||
1193 | 149 | child_pb.update(gettext('adding file'), | ||
1194 | 150 | num, len(new_paths)) | ||
1195 | 151 | full_path = self._tree.abspath(path) | ||
1196 | 152 | if trans_id in self._needs_rename: | ||
1197 | 153 | try: | ||
1198 | 154 | mover.rename(self._limbo_name(trans_id), full_path) | ||
1199 | 155 | except TransformRenameFailed as e: | ||
1200 | 156 | # We may be renaming a dangling inventory id | ||
1201 | 157 | if e.errno != errno.ENOENT: | ||
1202 | 158 | raise | ||
1203 | 159 | else: | ||
1204 | 160 | self.rename_count += 1 | ||
1205 | 161 | # TODO: if trans_id in self._observed_sha1s, we should | ||
1206 | 162 | # re-stat the final target, since ctime will be | ||
1207 | 163 | # updated by the change. | ||
1208 | 164 | if (trans_id in self._new_contents | ||
1209 | 165 | or self.path_changed(trans_id)): | ||
1210 | 166 | if trans_id in self._new_contents: | ||
1211 | 167 | modified_paths.append(full_path) | ||
1212 | 168 | if trans_id in self._new_executability: | ||
1213 | 169 | self._set_executability(path, trans_id) | ||
1214 | 170 | if trans_id in self._observed_sha1s: | ||
1215 | 171 | o_sha1, o_st_val = self._observed_sha1s[trans_id] | ||
1216 | 172 | st = osutils.lstat(full_path) | ||
1217 | 173 | self._observed_sha1s[trans_id] = (o_sha1, st) | ||
1218 | 174 | for path, trans_id in new_paths: | ||
1219 | 175 | # new_paths includes stuff like workingtree conflicts. Only the | ||
1220 | 176 | # stuff in new_contents actually comes from limbo. | ||
1221 | 177 | if trans_id in self._limbo_files: | ||
1222 | 178 | del self._limbo_files[trans_id] | ||
1223 | 179 | self._new_contents.clear() | ||
1224 | 180 | return modified_paths | ||
1225 | 181 | |||
1226 | 182 | def _inventory_altered(self): | ||
1227 | 183 | """Determine which trans_ids need new Inventory entries. | ||
1228 | 184 | |||
1229 | 185 | An new entry is needed when anything that would be reflected by an | ||
1230 | 186 | inventory entry changes, including file name, file_id, parent file_id, | ||
1231 | 187 | file kind, and the execute bit. | ||
1232 | 188 | |||
1233 | 189 | Some care is taken to return entries with real changes, not cases | ||
1234 | 190 | where the value is deleted and then restored to its original value, | ||
1235 | 191 | but some actually unchanged values may be returned. | ||
1236 | 192 | |||
1237 | 193 | :returns: A list of (path, trans_id) for all items requiring an | ||
1238 | 194 | inventory change. Ordered by path. | ||
1239 | 195 | """ | ||
1240 | 196 | changed_ids = set() | ||
1241 | 197 | # Find entries whose file_ids are new (or changed). | ||
1242 | 198 | new_file_id = set(t for t in self._new_id | ||
1243 | 199 | if self._new_id[t] != self.tree_file_id(t)) | ||
1244 | 200 | for id_set in [self._new_name, self._new_parent, new_file_id, | ||
1245 | 201 | self._new_executability]: | ||
1246 | 202 | changed_ids.update(id_set) | ||
1247 | 203 | # removing implies a kind change | ||
1248 | 204 | changed_kind = set(self._removed_contents) | ||
1249 | 205 | # so does adding | ||
1250 | 206 | changed_kind.intersection_update(self._new_contents) | ||
1251 | 207 | # Ignore entries that are already known to have changed. | ||
1252 | 208 | changed_kind.difference_update(changed_ids) | ||
1253 | 209 | # to keep only the truly changed ones | ||
1254 | 210 | changed_kind = (t for t in changed_kind | ||
1255 | 211 | if self.tree_kind(t) != self.final_kind(t)) | ||
1256 | 212 | # all kind changes will alter the inventory | ||
1257 | 213 | changed_ids.update(changed_kind) | ||
1258 | 214 | # To find entries with changed parent_ids, find parents which existed, | ||
1259 | 215 | # but changed file_id. | ||
1260 | 216 | # Now add all their children to the set. | ||
1261 | 217 | for parent_trans_id in new_file_id: | ||
1262 | 218 | changed_ids.update(self.iter_tree_children(parent_trans_id)) | ||
1263 | 219 | return sorted(FinalPaths(self).get_paths(changed_ids)) | ||
1264 | 220 | |||
1265 | 221 | def _generate_transform_changes(self): | ||
1266 | 222 | """Generate an inventory delta for the current transform.""" | ||
1267 | 223 | changes = [] | ||
1268 | 224 | new_paths = self._inventory_altered() | ||
1269 | 225 | total_entries = len(new_paths) + len(self._removed_id) | ||
1270 | 226 | with ui.ui_factory.nested_progress_bar() as child_pb: | ||
1271 | 227 | for num, trans_id in enumerate(self._removed_id): | ||
1272 | 228 | if (num % 10) == 0: | ||
1273 | 229 | child_pb.update(gettext('removing file'), | ||
1274 | 230 | num, total_entries) | ||
1275 | 231 | if trans_id == self._new_root: | ||
1276 | 232 | file_id = self._tree.path2id('') | ||
1277 | 233 | else: | ||
1278 | 234 | file_id = self.tree_file_id(trans_id) | ||
1279 | 235 | # File-id isn't really being deleted, just moved | ||
1280 | 236 | if file_id in self._r_new_id: | ||
1281 | 237 | continue | ||
1282 | 238 | path = self._tree_id_paths[trans_id] | ||
1283 | 239 | changes.append((path, None, None)) | ||
1284 | 240 | new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in | ||
1285 | 241 | new_paths) | ||
1286 | 242 | for num, (path, trans_id) in enumerate(new_paths): | ||
1287 | 243 | if (num % 10) == 0: | ||
1288 | 244 | child_pb.update(gettext('adding file'), | ||
1289 | 245 | num + len(self._removed_id), total_entries) | ||
1290 | 246 | file_id = new_path_file_ids[trans_id] | ||
1291 | 247 | if file_id is None: | ||
1292 | 248 | continue | ||
1293 | 249 | kind = self.final_kind(trans_id) | ||
1294 | 250 | if kind is None: | ||
1295 | 251 | kind = self._tree.stored_kind(self._tree.id2path(file_id)) | ||
1296 | 252 | parent_trans_id = self.final_parent(trans_id) | ||
1297 | 253 | parent_file_id = new_path_file_ids.get(parent_trans_id) | ||
1298 | 254 | if parent_file_id is None: | ||
1299 | 255 | parent_file_id = self.final_file_id(parent_trans_id) | ||
1300 | 256 | if trans_id in self._new_reference_revision: | ||
1301 | 257 | new_entry = inventory.TreeReference( | ||
1302 | 258 | file_id, | ||
1303 | 259 | self._new_name[trans_id], | ||
1304 | 260 | self.final_file_id(self._new_parent[trans_id]), | ||
1305 | 261 | None, self._new_reference_revision[trans_id]) | ||
1306 | 262 | else: | ||
1307 | 263 | new_entry = inventory.make_entry(kind, | ||
1308 | 264 | self.final_name(trans_id), | ||
1309 | 265 | parent_file_id, file_id) | ||
1310 | 266 | try: | ||
1311 | 267 | old_path = self._tree.id2path(new_entry.file_id) | ||
1312 | 268 | except errors.NoSuchId: | ||
1313 | 269 | old_path = None | ||
1314 | 270 | new_executability = self._new_executability.get(trans_id) | ||
1315 | 271 | if new_executability is not None: | ||
1316 | 272 | new_entry.executable = new_executability | ||
1317 | 273 | changes.append( | ||
1318 | 274 | (old_path, path, new_entry)) | ||
1319 | 275 | return changes | ||
1320 | 0 | 276 | ||
1321 | === modified file 'breezy/git/tree.py' | |||
1322 | --- breezy/git/tree.py 2020-07-04 23:16:47 +0000 | |||
1323 | +++ breezy/git/tree.py 2020-07-05 13:48:17 +0000 | |||
1324 | @@ -725,8 +725,8 @@ | |||
1325 | 725 | yield (path_decoded, parent_id), children | 725 | yield (path_decoded, parent_id), children |
1326 | 726 | 726 | ||
1327 | 727 | def preview_transform(self, pb=None): | 727 | def preview_transform(self, pb=None): |
1330 | 728 | from ..transform import TransformPreview | 728 | from .transform import GitTransformPreview |
1331 | 729 | return TransformPreview(self, pb=pb) | 729 | return GitTransformPreview(self, pb=pb) |
1332 | 730 | 730 | ||
1333 | 731 | 731 | ||
1334 | 732 | def tree_delta_from_git_changes(changes, mappings, | 732 | def tree_delta_from_git_changes(changes, mappings, |
1335 | @@ -1635,13 +1635,12 @@ | |||
1336 | 1635 | raise NotImplementedError(self._live_entry) | 1635 | raise NotImplementedError(self._live_entry) |
1337 | 1636 | 1636 | ||
1338 | 1637 | def transform(self, pb=None): | 1637 | def transform(self, pb=None): |
1341 | 1638 | from ..transform import TreeTransform | 1638 | from .transform import GitTreeTransform |
1342 | 1639 | return TreeTransform(self, pb=pb) | 1639 | return GitTreeTransform(self, pb=pb) |
1343 | 1640 | 1640 | ||
1344 | 1641 | def preview_transform(self, pb=None): | 1641 | def preview_transform(self, pb=None): |
1348 | 1642 | from ..transform import TransformPreview | 1642 | from ..transform import GitTransformPreview |
1349 | 1643 | return TransformPreview(self, pb=pb) | 1643 | return GitTransformPreview(self, pb=pb) |
1347 | 1644 | |||
1350 | 1645 | 1644 | ||
1351 | 1646 | 1645 | ||
1352 | 1647 | class InterToIndexGitTree(InterGitTrees): | 1646 | class InterToIndexGitTree(InterGitTrees): |
1353 | 1648 | 1647 | ||
1354 | === modified file 'breezy/git/workingtree.py' | |||
1355 | --- breezy/git/workingtree.py 2020-07-04 14:58:52 +0000 | |||
1356 | +++ breezy/git/workingtree.py 2020-07-05 13:48:17 +0000 | |||
1357 | @@ -224,10 +224,6 @@ | |||
1358 | 224 | else: | 224 | else: |
1359 | 225 | self.case_sensitive = False | 225 | self.case_sensitive = False |
1360 | 226 | 226 | ||
1361 | 227 | def transform(self, pb=None): | ||
1362 | 228 | from ..transform import TreeTransform | ||
1363 | 229 | return TreeTransform(self, pb=pb) | ||
1364 | 230 | |||
1365 | 231 | def merge_modified(self): | 227 | def merge_modified(self): |
1366 | 232 | return {} | 228 | return {} |
1367 | 233 | 229 | ||
1368 | @@ -1075,8 +1071,8 @@ | |||
1369 | 1075 | def store_uncommitted(self): | 1071 | def store_uncommitted(self): |
1370 | 1076 | raise errors.StoringUncommittedNotSupported(self) | 1072 | raise errors.StoringUncommittedNotSupported(self) |
1371 | 1077 | 1073 | ||
1374 | 1078 | def apply_inventory_delta(self, changes): | 1074 | def _apply_transform_delta(self, changes): |
1375 | 1079 | for (old_path, new_path, file_id, ie) in changes: | 1075 | for (old_path, new_path, ie) in changes: |
1376 | 1080 | if old_path is not None: | 1076 | if old_path is not None: |
1377 | 1081 | (index, old_subpath) = self._lookup_index( | 1077 | (index, old_subpath) = self._lookup_index( |
1378 | 1082 | encode_git_path(old_path)) | 1078 | encode_git_path(old_path)) |
1379 | 1083 | 1079 | ||
1380 | === modified file 'breezy/merge.py' | |||
1381 | --- breezy/merge.py 2020-07-04 18:40:42 +0000 | |||
1382 | +++ breezy/merge.py 2020-07-05 13:48:17 +0000 | |||
1383 | @@ -31,7 +31,6 @@ | |||
1384 | 31 | revision as _mod_revision, | 31 | revision as _mod_revision, |
1385 | 32 | textfile, | 32 | textfile, |
1386 | 33 | trace, | 33 | trace, |
1387 | 34 | transform, | ||
1388 | 35 | tree as _mod_tree, | 34 | tree as _mod_tree, |
1389 | 36 | tsort, | 35 | tsort, |
1390 | 37 | ui, | 36 | ui, |
1391 | @@ -48,6 +47,7 @@ | |||
1392 | 48 | errors, | 47 | errors, |
1393 | 49 | hooks, | 48 | hooks, |
1394 | 50 | registry, | 49 | registry, |
1395 | 50 | transform, | ||
1396 | 51 | ) | 51 | ) |
1397 | 52 | from .sixish import ( | 52 | from .sixish import ( |
1398 | 53 | viewitems, | 53 | viewitems, |
1399 | @@ -780,10 +780,10 @@ | |||
1400 | 780 | 780 | ||
1401 | 781 | def _compute_transform(self): | 781 | def _compute_transform(self): |
1402 | 782 | if self._lca_trees is None: | 782 | if self._lca_trees is None: |
1404 | 783 | entries = self._entries3() | 783 | entries = list(self._entries3()) |
1405 | 784 | resolver = self._three_way | 784 | resolver = self._three_way |
1406 | 785 | else: | 785 | else: |
1408 | 786 | entries = self._entries_lca() | 786 | entries = list(self._entries_lca()) |
1409 | 787 | resolver = self._lca_multi_way | 787 | resolver = self._lca_multi_way |
1410 | 788 | # Prepare merge hooks | 788 | # Prepare merge hooks |
1411 | 789 | factories = Merger.hooks['merge_file_content'] | 789 | factories = Merger.hooks['merge_file_content'] |
1412 | @@ -794,7 +794,6 @@ | |||
1413 | 794 | for num, (file_id, changed, paths3, parents3, names3, | 794 | for num, (file_id, changed, paths3, parents3, names3, |
1414 | 795 | executable3) in enumerate(entries): | 795 | executable3) in enumerate(entries): |
1415 | 796 | trans_id = self.tt.trans_id_file_id(file_id) | 796 | trans_id = self.tt.trans_id_file_id(file_id) |
1416 | 797 | |||
1417 | 798 | # Try merging each entry | 797 | # Try merging each entry |
1418 | 799 | child_pb.update(gettext('Preparing file merge'), | 798 | child_pb.update(gettext('Preparing file merge'), |
1419 | 800 | num, len(entries)) | 799 | num, len(entries)) |
1420 | @@ -835,7 +834,6 @@ | |||
1421 | 835 | other and this. names3 is a tuple of names for base, other and this. | 834 | other and this. names3 is a tuple of names for base, other and this. |
1422 | 836 | executable3 is a tuple of execute-bit values for base, other and this. | 835 | executable3 is a tuple of execute-bit values for base, other and this. |
1423 | 837 | """ | 836 | """ |
1424 | 838 | result = [] | ||
1425 | 839 | iterator = self.other_tree.iter_changes(self.base_tree, | 837 | iterator = self.other_tree.iter_changes(self.base_tree, |
1426 | 840 | specific_files=self.interesting_files, | 838 | specific_files=self.interesting_files, |
1427 | 841 | extra_trees=[self.this_tree]) | 839 | extra_trees=[self.this_tree]) |
1428 | @@ -863,10 +861,9 @@ | |||
1429 | 863 | names3 = change.name + (this_name,) | 861 | names3 = change.name + (this_name,) |
1430 | 864 | paths3 = change.path + (this_path, ) | 862 | paths3 = change.path + (this_path, ) |
1431 | 865 | executable3 = change.executable + (this_executable,) | 863 | executable3 = change.executable + (this_executable,) |
1433 | 866 | result.append( | 864 | yield ( |
1434 | 867 | (change.file_id, change.changed_content, paths3, | 865 | (change.file_id, change.changed_content, paths3, |
1435 | 868 | parents3, names3, executable3)) | 866 | parents3, names3, executable3)) |
1436 | 869 | return result | ||
1437 | 870 | 867 | ||
1438 | 871 | def _entries_lca(self): | 868 | def _entries_lca(self): |
1439 | 872 | """Gather data about files modified between multiple trees. | 869 | """Gather data about files modified between multiple trees. |
1440 | @@ -895,7 +892,6 @@ | |||
1441 | 895 | self.interesting_files, lookup_trees) | 892 | self.interesting_files, lookup_trees) |
1442 | 896 | else: | 893 | else: |
1443 | 897 | interesting_files = None | 894 | interesting_files = None |
1444 | 898 | result = [] | ||
1445 | 899 | from .multiwalker import MultiWalker | 895 | from .multiwalker import MultiWalker |
1446 | 900 | walker = MultiWalker(self.other_tree, self._lca_trees) | 896 | walker = MultiWalker(self.other_tree, self._lca_trees) |
1447 | 901 | 897 | ||
1448 | @@ -1039,7 +1035,7 @@ | |||
1449 | 1039 | raise AssertionError('unhandled kind: %s' % other_ie.kind) | 1035 | raise AssertionError('unhandled kind: %s' % other_ie.kind) |
1450 | 1040 | 1036 | ||
1451 | 1041 | # If we have gotten this far, that means something has changed | 1037 | # If we have gotten this far, that means something has changed |
1453 | 1042 | result.append((file_id, content_changed, | 1038 | yield (file_id, content_changed, |
1454 | 1043 | ((base_path, lca_paths), | 1039 | ((base_path, lca_paths), |
1455 | 1044 | other_path, this_path), | 1040 | other_path, this_path), |
1456 | 1045 | ((base_ie.parent_id, lca_parent_ids), | 1041 | ((base_ie.parent_id, lca_parent_ids), |
1457 | @@ -1048,8 +1044,7 @@ | |||
1458 | 1048 | other_ie.name, this_ie.name), | 1044 | other_ie.name, this_ie.name), |
1459 | 1049 | ((base_ie.executable, lca_executable), | 1045 | ((base_ie.executable, lca_executable), |
1460 | 1050 | other_ie.executable, this_ie.executable) | 1046 | other_ie.executable, this_ie.executable) |
1463 | 1051 | )) | 1047 | ) |
1462 | 1052 | return result | ||
1464 | 1053 | 1048 | ||
1465 | 1054 | def write_modified(self, results): | 1049 | def write_modified(self, results): |
1466 | 1055 | if not self.working_tree.supports_merge_modified(): | 1050 | if not self.working_tree.supports_merge_modified(): |
1467 | 1056 | 1051 | ||
1468 | === modified file 'breezy/tests/per_tree/test_path_content_summary.py' | |||
1469 | --- breezy/tests/per_tree/test_path_content_summary.py 2020-07-04 00:38:37 +0000 | |||
1470 | +++ breezy/tests/per_tree/test_path_content_summary.py 2020-07-05 13:48:17 +0000 | |||
1471 | @@ -21,9 +21,11 @@ | |||
1472 | 21 | from breezy import ( | 21 | from breezy import ( |
1473 | 22 | osutils, | 22 | osutils, |
1474 | 23 | tests, | 23 | tests, |
1475 | 24 | transform, | ||
1476 | 25 | ) | 24 | ) |
1477 | 26 | 25 | ||
1478 | 26 | from breezy.bzr.transform import ( | ||
1479 | 27 | _PreviewTree, | ||
1480 | 28 | ) | ||
1481 | 27 | from breezy.tests import ( | 29 | from breezy.tests import ( |
1482 | 28 | features, | 30 | features, |
1483 | 29 | per_tree, | 31 | per_tree, |
1484 | @@ -112,7 +114,7 @@ | |||
1485 | 112 | self.assertEqual('missing', summary[0]) | 114 | self.assertEqual('missing', summary[0]) |
1486 | 113 | self.assertIs(None, summary[2]) | 115 | self.assertIs(None, summary[2]) |
1487 | 114 | self.assertIs(None, summary[3]) | 116 | self.assertIs(None, summary[3]) |
1489 | 115 | elif isinstance(tree, transform._PreviewTree): | 117 | elif isinstance(tree, _PreviewTree): |
1490 | 116 | self.expectFailure('PreviewTree returns "missing" for unversioned' | 118 | self.expectFailure('PreviewTree returns "missing" for unversioned' |
1491 | 117 | 'files', self.assertEqual, 'file', summary[0]) | 119 | 'files', self.assertEqual, 'file', summary[0]) |
1492 | 118 | self.assertEqual('file', summary[0]) | 120 | self.assertEqual('file', summary[0]) |
1493 | 119 | 121 | ||
1494 | === modified file 'breezy/tests/per_tree/test_symlinks.py' | |||
1495 | --- breezy/tests/per_tree/test_symlinks.py 2019-06-29 21:31:14 +0000 | |||
1496 | +++ breezy/tests/per_tree/test_symlinks.py 2020-07-05 13:48:17 +0000 | |||
1497 | @@ -27,7 +27,7 @@ | |||
1498 | 27 | ) | 27 | ) |
1499 | 28 | from breezy.mutabletree import MutableTree | 28 | from breezy.mutabletree import MutableTree |
1500 | 29 | from breezy.tests import TestSkipped | 29 | from breezy.tests import TestSkipped |
1502 | 30 | from breezy.transform import _PreviewTree | 30 | from breezy.bzr.transform import _PreviewTree |
1503 | 31 | from breezy.tests import ( | 31 | from breezy.tests import ( |
1504 | 32 | features, | 32 | features, |
1505 | 33 | ) | 33 | ) |
1506 | 34 | 34 | ||
1507 | === modified file 'breezy/tests/per_workingtree/test_transform.py' | |||
1508 | --- breezy/tests/per_workingtree/test_transform.py 2020-07-05 00:03:01 +0000 | |||
1509 | +++ breezy/tests/per_workingtree/test_transform.py 2020-07-05 13:48:17 +0000 | |||
1510 | @@ -70,6 +70,7 @@ | |||
1511 | 70 | MalformedTransform, | 70 | MalformedTransform, |
1512 | 71 | NoFinalPath, | 71 | NoFinalPath, |
1513 | 72 | ReusingTransform, | 72 | ReusingTransform, |
1514 | 73 | TransformRenameFailed, | ||
1515 | 73 | ) | 74 | ) |
1516 | 74 | 75 | ||
1517 | 75 | from breezy.tests.per_workingtree import TestCaseWithWorkingTree | 76 | from breezy.tests.per_workingtree import TestCaseWithWorkingTree |
1518 | @@ -335,7 +336,7 @@ | |||
1519 | 335 | with transform: | 336 | with transform: |
1520 | 336 | transform.delete_contents(root) | 337 | transform.delete_contents(root) |
1521 | 337 | e = self.assertRaises(AssertionError, self.assertRaises, | 338 | e = self.assertRaises(AssertionError, self.assertRaises, |
1523 | 338 | errors.TransformRenameFailed, | 339 | TransformRenameFailed, |
1524 | 339 | transform.apply) | 340 | transform.apply) |
1525 | 340 | self.assertContainsRe('TransformRenameFailed not raised', str(e)) | 341 | self.assertContainsRe('TransformRenameFailed not raised', str(e)) |
1526 | 341 | 342 | ||
1527 | @@ -1032,7 +1033,7 @@ | |||
1528 | 1032 | file_trans_id = rename_transform.trans_id_file_id(b'myfile-id') | 1033 | file_trans_id = rename_transform.trans_id_file_id(b'myfile-id') |
1529 | 1033 | dir_id = rename_transform.trans_id_file_id(b'first-id') | 1034 | dir_id = rename_transform.trans_id_file_id(b'first-id') |
1530 | 1034 | rename_transform.adjust_path('newname', dir_id, file_trans_id) | 1035 | rename_transform.adjust_path('newname', dir_id, file_trans_id) |
1532 | 1035 | e = self.assertRaises(errors.TransformRenameFailed, | 1036 | e = self.assertRaises(TransformRenameFailed, |
1533 | 1036 | rename_transform.apply) | 1037 | rename_transform.apply) |
1534 | 1037 | # On nix looks like: | 1038 | # On nix looks like: |
1535 | 1038 | # "Failed to rename .../work/.bzr/checkout/limbo/new-1 | 1039 | # "Failed to rename .../work/.bzr/checkout/limbo/new-1 |
1536 | 1039 | 1040 | ||
1537 | === modified file 'breezy/tests/test_errors.py' | |||
1538 | --- breezy/tests/test_errors.py 2020-06-25 21:17:44 +0000 | |||
1539 | +++ breezy/tests/test_errors.py 2020-07-05 13:48:17 +0000 | |||
1540 | @@ -515,12 +515,6 @@ | |||
1541 | 515 | str(e), | 515 | str(e), |
1542 | 516 | r'Cannot bind address "example\.com:22":.*Permission denied') | 516 | r'Cannot bind address "example\.com:22":.*Permission denied') |
1543 | 517 | 517 | ||
1544 | 518 | def test_transform_rename_failed(self): | ||
1545 | 519 | e = errors.TransformRenameFailed(u"from", u"to", "readonly file", 2) | ||
1546 | 520 | self.assertEqual( | ||
1547 | 521 | u"Failed to rename from to to: readonly file", | ||
1548 | 522 | str(e)) | ||
1549 | 523 | |||
1550 | 524 | 518 | ||
1551 | 525 | class TestErrorsUsingTransport(tests.TestCaseWithMemoryTransport): | 519 | class TestErrorsUsingTransport(tests.TestCaseWithMemoryTransport): |
1552 | 526 | """Tests for errors that need to use a branch or repo.""" | 520 | """Tests for errors that need to use a branch or repo.""" |
1553 | 527 | 521 | ||
1554 | === modified file 'breezy/tests/test_transform.py' | |||
1555 | --- breezy/tests/test_transform.py 2020-07-05 00:03:01 +0000 | |||
1556 | +++ breezy/tests/test_transform.py 2020-07-05 13:48:17 +0000 | |||
1557 | @@ -87,9 +87,11 @@ | |||
1558 | 87 | MalformedTransform, | 87 | MalformedTransform, |
1559 | 88 | NoFinalPath, | 88 | NoFinalPath, |
1560 | 89 | ReusingTransform, | 89 | ReusingTransform, |
1563 | 90 | TransformPreview, | 90 | TransformRenameFailed, |
1562 | 91 | TreeTransform, | ||
1564 | 92 | ) | 91 | ) |
1565 | 92 | from ..bzr.transform import ( | ||
1566 | 93 | InventoryTreeTransform as TreeTransform, | ||
1567 | 94 | ) | ||
1568 | 93 | 95 | ||
1569 | 94 | 96 | ||
1570 | 95 | class TransformGroup(object): | 97 | class TransformGroup(object): |
1571 | @@ -109,38 +111,6 @@ | |||
1572 | 109 | return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7) | 111 | return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7) |
1573 | 110 | 112 | ||
1574 | 111 | 113 | ||
1575 | 112 | class TestInventoryAltered(tests.TestCaseWithTransport): | ||
1576 | 113 | |||
1577 | 114 | def test_inventory_altered_unchanged(self): | ||
1578 | 115 | tree = self.make_branch_and_tree('tree') | ||
1579 | 116 | self.build_tree(['tree/foo']) | ||
1580 | 117 | tree.add('foo', b'foo-id') | ||
1581 | 118 | with tree.preview_transform() as tt: | ||
1582 | 119 | self.assertEqual([], tt._inventory_altered()) | ||
1583 | 120 | |||
1584 | 121 | def test_inventory_altered_changed_parent_id(self): | ||
1585 | 122 | tree = self.make_branch_and_tree('tree') | ||
1586 | 123 | self.build_tree(['tree/foo']) | ||
1587 | 124 | tree.add('foo', b'foo-id') | ||
1588 | 125 | with tree.preview_transform() as tt: | ||
1589 | 126 | tt.unversion_file(tt.root) | ||
1590 | 127 | tt.version_file(tt.root, file_id=b'new-id') | ||
1591 | 128 | foo_trans_id = tt.trans_id_tree_path('foo') | ||
1592 | 129 | foo_tuple = ('foo', foo_trans_id) | ||
1593 | 130 | root_tuple = ('', tt.root) | ||
1594 | 131 | self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered()) | ||
1595 | 132 | |||
1596 | 133 | def test_inventory_altered_noop_changed_parent_id(self): | ||
1597 | 134 | tree = self.make_branch_and_tree('tree') | ||
1598 | 135 | self.build_tree(['tree/foo']) | ||
1599 | 136 | tree.add('foo', b'foo-id') | ||
1600 | 137 | with tree.preview_transform() as tt: | ||
1601 | 138 | tt.unversion_file(tt.root) | ||
1602 | 139 | tt.version_file(tt.root, file_id=tree.path2id('')) | ||
1603 | 140 | tt.trans_id_tree_path('foo') | ||
1604 | 141 | self.assertEqual([], tt._inventory_altered()) | ||
1605 | 142 | |||
1606 | 143 | |||
1607 | 144 | class TestTransformMerge(TestCaseInTempDir): | 114 | class TestTransformMerge(TestCaseInTempDir): |
1608 | 145 | 115 | ||
1609 | 146 | def test_text_merge(self): | 116 | def test_text_merge(self): |
1610 | @@ -1603,3 +1573,12 @@ | |||
1611 | 1603 | """If the file to be linked is unmodified, link""" | 1573 | """If the file to be linked is unmodified, link""" |
1612 | 1604 | transform.link_tree(self.child_tree, self.parent_tree) | 1574 | transform.link_tree(self.child_tree, self.parent_tree) |
1613 | 1605 | self.assertTrue(self.hardlinked()) | 1575 | self.assertTrue(self.hardlinked()) |
1614 | 1576 | |||
1615 | 1577 | |||
1616 | 1578 | class ErrorTests(tests.TestCase): | ||
1617 | 1579 | |||
1618 | 1580 | def test_transform_rename_failed(self): | ||
1619 | 1581 | e = TransformRenameFailed(u"from", u"to", "readonly file", 2) | ||
1620 | 1582 | self.assertEqual( | ||
1621 | 1583 | u"Failed to rename from to to: readonly file", | ||
1622 | 1584 | str(e)) | ||
1623 | 1606 | 1585 | ||
1624 | === modified file 'breezy/transform.py' | |||
1625 | --- breezy/transform.py 2020-07-04 19:23:30 +0000 | |||
1626 | +++ breezy/transform.py 2020-07-05 13:48:17 +0000 | |||
1627 | @@ -29,29 +29,20 @@ | |||
1628 | 29 | osutils, | 29 | osutils, |
1629 | 30 | registry, | 30 | registry, |
1630 | 31 | trace, | 31 | trace, |
1631 | 32 | tree, | ||
1632 | 33 | ) | 32 | ) |
1633 | 34 | lazy_import.lazy_import(globals(), """ | 33 | lazy_import.lazy_import(globals(), """ |
1634 | 35 | from breezy import ( | 34 | from breezy import ( |
1635 | 36 | annotate, | ||
1636 | 37 | cleanup, | 35 | cleanup, |
1637 | 38 | commit, | ||
1638 | 39 | conflicts, | 36 | conflicts, |
1639 | 40 | lock, | ||
1640 | 41 | multiparent, | 37 | multiparent, |
1641 | 42 | revision as _mod_revision, | 38 | revision as _mod_revision, |
1642 | 43 | ui, | 39 | ui, |
1643 | 44 | urlutils, | 40 | urlutils, |
1644 | 45 | ) | 41 | ) |
1645 | 46 | from breezy.i18n import gettext | 42 | from breezy.i18n import gettext |
1646 | 47 | from breezy.bzr import ( | ||
1647 | 48 | inventory, | ||
1648 | 49 | inventorytree, | ||
1649 | 50 | ) | ||
1650 | 51 | """) | 43 | """) |
1651 | 52 | 44 | ||
1652 | 53 | from .errors import (DuplicateKey, | 45 | from .errors import (DuplicateKey, |
1653 | 54 | CantMoveRoot, | ||
1654 | 55 | BzrError, InternalBzrError) | 46 | BzrError, InternalBzrError) |
1655 | 56 | from .filters import filtered_output_bytes, ContentFilterContext | 47 | from .filters import filtered_output_bytes, ContentFilterContext |
1656 | 57 | from .mutabletree import MutableTree | 48 | from .mutabletree import MutableTree |
1657 | @@ -101,6 +92,11 @@ | |||
1658 | 101 | _fmt = "Tree transform is malformed %(conflicts)r" | 92 | _fmt = "Tree transform is malformed %(conflicts)r" |
1659 | 102 | 93 | ||
1660 | 103 | 94 | ||
1661 | 95 | class CantMoveRoot(BzrError): | ||
1662 | 96 | |||
1663 | 97 | _fmt = "Moving the root directory is not supported at this time" | ||
1664 | 98 | |||
1665 | 99 | |||
1666 | 104 | class ImmortalLimbo(BzrError): | 100 | class ImmortalLimbo(BzrError): |
1667 | 105 | 101 | ||
1668 | 106 | _fmt = """Unable to delete transform temporary directory %(limbo_dir)s. | 102 | _fmt = """Unable to delete transform temporary directory %(limbo_dir)s. |
1669 | @@ -112,6 +108,17 @@ | |||
1670 | 112 | self.limbo_dir = limbo_dir | 108 | self.limbo_dir = limbo_dir |
1671 | 113 | 109 | ||
1672 | 114 | 110 | ||
1673 | 111 | class TransformRenameFailed(BzrError): | ||
1674 | 112 | |||
1675 | 113 | _fmt = "Failed to rename %(from_path)s to %(to_path)s: %(why)s" | ||
1676 | 114 | |||
1677 | 115 | def __init__(self, from_path, to_path, why, errno): | ||
1678 | 116 | self.from_path = from_path | ||
1679 | 117 | self.to_path = to_path | ||
1680 | 118 | self.why = why | ||
1681 | 119 | self.errno = errno | ||
1682 | 120 | |||
1683 | 121 | |||
1684 | 115 | def unique_add(map, key, value): | 122 | def unique_add(map, key, value): |
1685 | 116 | if key in map: | 123 | if key in map: |
1686 | 117 | raise DuplicateKey(key=key) | 124 | raise DuplicateKey(key=key) |
1687 | @@ -410,16 +417,11 @@ | |||
1688 | 410 | 417 | ||
1689 | 411 | def version_file(self, trans_id, file_id=None): | 418 | def version_file(self, trans_id, file_id=None): |
1690 | 412 | """Schedule a file to become versioned.""" | 419 | """Schedule a file to become versioned.""" |
1695 | 413 | if file_id is None: | 420 | raise NotImplementedError(self.version_file) |
1692 | 414 | raise ValueError() | ||
1693 | 415 | unique_add(self._new_id, trans_id, file_id) | ||
1694 | 416 | unique_add(self._r_new_id, file_id, trans_id) | ||
1696 | 417 | 421 | ||
1697 | 418 | def cancel_versioning(self, trans_id): | 422 | def cancel_versioning(self, trans_id): |
1698 | 419 | """Undo a previous versioning of a file""" | 423 | """Undo a previous versioning of a file""" |
1702 | 420 | file_id = self._new_id[trans_id] | 424 | raise NotImplementedError(self.cancel_versioning) |
1700 | 421 | del self._new_id[trans_id] | ||
1701 | 422 | del self._r_new_id[file_id] | ||
1703 | 423 | 425 | ||
1704 | 424 | def new_paths(self, filesystem_only=False): | 426 | def new_paths(self, filesystem_only=False): |
1705 | 425 | """Determine the paths of all new and changed files. | 427 | """Determine the paths of all new and changed files. |
1706 | @@ -442,45 +444,6 @@ | |||
1707 | 442 | new_ids.update(id_set) | 444 | new_ids.update(id_set) |
1708 | 443 | return sorted(FinalPaths(self).get_paths(new_ids)) | 445 | return sorted(FinalPaths(self).get_paths(new_ids)) |
1709 | 444 | 446 | ||
1710 | 445 | def _inventory_altered(self): | ||
1711 | 446 | """Determine which trans_ids need new Inventory entries. | ||
1712 | 447 | |||
1713 | 448 | An new entry is needed when anything that would be reflected by an | ||
1714 | 449 | inventory entry changes, including file name, file_id, parent file_id, | ||
1715 | 450 | file kind, and the execute bit. | ||
1716 | 451 | |||
1717 | 452 | Some care is taken to return entries with real changes, not cases | ||
1718 | 453 | where the value is deleted and then restored to its original value, | ||
1719 | 454 | but some actually unchanged values may be returned. | ||
1720 | 455 | |||
1721 | 456 | :returns: A list of (path, trans_id) for all items requiring an | ||
1722 | 457 | inventory change. Ordered by path. | ||
1723 | 458 | """ | ||
1724 | 459 | changed_ids = set() | ||
1725 | 460 | # Find entries whose file_ids are new (or changed). | ||
1726 | 461 | new_file_id = set(t for t in self._new_id | ||
1727 | 462 | if self._new_id[t] != self.tree_file_id(t)) | ||
1728 | 463 | for id_set in [self._new_name, self._new_parent, new_file_id, | ||
1729 | 464 | self._new_executability]: | ||
1730 | 465 | changed_ids.update(id_set) | ||
1731 | 466 | # removing implies a kind change | ||
1732 | 467 | changed_kind = set(self._removed_contents) | ||
1733 | 468 | # so does adding | ||
1734 | 469 | changed_kind.intersection_update(self._new_contents) | ||
1735 | 470 | # Ignore entries that are already known to have changed. | ||
1736 | 471 | changed_kind.difference_update(changed_ids) | ||
1737 | 472 | # to keep only the truly changed ones | ||
1738 | 473 | changed_kind = (t for t in changed_kind | ||
1739 | 474 | if self.tree_kind(t) != self.final_kind(t)) | ||
1740 | 475 | # all kind changes will alter the inventory | ||
1741 | 476 | changed_ids.update(changed_kind) | ||
1742 | 477 | # To find entries with changed parent_ids, find parents which existed, | ||
1743 | 478 | # but changed file_id. | ||
1744 | 479 | # Now add all their children to the set. | ||
1745 | 480 | for parent_trans_id in new_file_id: | ||
1746 | 481 | changed_ids.update(self.iter_tree_children(parent_trans_id)) | ||
1747 | 482 | return sorted(FinalPaths(self).get_paths(changed_ids)) | ||
1748 | 483 | |||
1749 | 484 | def final_kind(self, trans_id): | 447 | def final_kind(self, trans_id): |
1750 | 485 | """Determine the final file kind, after any changes applied. | 448 | """Determine the final file kind, after any changes applied. |
1751 | 486 | 449 | ||
1752 | @@ -591,7 +554,6 @@ | |||
1753 | 591 | conflicts.extend(self._unversioned_parents(by_parent)) | 554 | conflicts.extend(self._unversioned_parents(by_parent)) |
1754 | 592 | conflicts.extend(self._parent_loops()) | 555 | conflicts.extend(self._parent_loops()) |
1755 | 593 | conflicts.extend(self._duplicate_entries(by_parent)) | 556 | conflicts.extend(self._duplicate_entries(by_parent)) |
1756 | 594 | conflicts.extend(self._duplicate_ids()) | ||
1757 | 595 | conflicts.extend(self._parent_type_conflicts(by_parent)) | 557 | conflicts.extend(self._parent_type_conflicts(by_parent)) |
1758 | 596 | conflicts.extend(self._improper_versioning()) | 558 | conflicts.extend(self._improper_versioning()) |
1759 | 597 | conflicts.extend(self._executability_conflicts()) | 559 | conflicts.extend(self._executability_conflicts()) |
1760 | @@ -773,24 +735,6 @@ | |||
1761 | 773 | last_trans_id = trans_id | 735 | last_trans_id = trans_id |
1762 | 774 | return conflicts | 736 | return conflicts |
1763 | 775 | 737 | ||
1764 | 776 | def _duplicate_ids(self): | ||
1765 | 777 | """Each inventory id may only be used once""" | ||
1766 | 778 | conflicts = [] | ||
1767 | 779 | try: | ||
1768 | 780 | all_ids = self._tree.all_file_ids() | ||
1769 | 781 | except errors.UnsupportedOperation: | ||
1770 | 782 | # it's okay for non-file-id trees to raise UnsupportedOperation. | ||
1771 | 783 | return [] | ||
1772 | 784 | removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in | ||
1773 | 785 | self._removed_id)) | ||
1774 | 786 | active_tree_ids = all_ids.difference(removed_tree_ids) | ||
1775 | 787 | for trans_id, file_id in viewitems(self._new_id): | ||
1776 | 788 | if file_id in active_tree_ids: | ||
1777 | 789 | path = self._tree.id2path(file_id) | ||
1778 | 790 | old_trans_id = self.trans_id_tree_path(path) | ||
1779 | 791 | conflicts.append(('duplicate id', old_trans_id, trans_id)) | ||
1780 | 792 | return conflicts | ||
1781 | 793 | |||
1782 | 794 | def _parent_type_conflicts(self, by_parent): | 738 | def _parent_type_conflicts(self, by_parent): |
1783 | 795 | """Children must have a directory parent""" | 739 | """Children must have a directory parent""" |
1784 | 796 | conflicts = [] | 740 | conflicts = [] |
1785 | @@ -1075,7 +1019,7 @@ | |||
1786 | 1075 | The tree is a snapshot, and altering the TreeTransform will invalidate | 1019 | The tree is a snapshot, and altering the TreeTransform will invalidate |
1787 | 1076 | it. | 1020 | it. |
1788 | 1077 | """ | 1021 | """ |
1790 | 1078 | return _PreviewTree(self) | 1022 | raise NotImplementedError(self.get_preview) |
1791 | 1079 | 1023 | ||
1792 | 1080 | def commit(self, branch, message, merge_parents=None, strict=False, | 1024 | def commit(self, branch, message, merge_parents=None, strict=False, |
1793 | 1081 | timestamp=None, timezone=None, committer=None, authors=None, | 1025 | timestamp=None, timezone=None, committer=None, authors=None, |
1794 | @@ -1120,6 +1064,7 @@ | |||
1795 | 1120 | if self._tree.get_revision_id() != last_rev_id: | 1064 | if self._tree.get_revision_id() != last_rev_id: |
1796 | 1121 | raise ValueError('TreeTransform not based on branch basis: %s' % | 1065 | raise ValueError('TreeTransform not based on branch basis: %s' % |
1797 | 1122 | self._tree.get_revision_id().decode('utf-8')) | 1066 | self._tree.get_revision_id().decode('utf-8')) |
1798 | 1067 | from . import commit | ||
1799 | 1123 | revprops = commit.Commit.update_revprops(revprops, branch, authors) | 1068 | revprops = commit.Commit.update_revprops(revprops, branch, authors) |
1800 | 1124 | builder = branch.get_commit_builder(parent_ids, | 1069 | builder = branch.get_commit_builder(parent_ids, |
1801 | 1125 | timestamp=timestamp, | 1070 | timestamp=timestamp, |
1802 | @@ -1836,749 +1781,7 @@ | |||
1803 | 1836 | calculating one. | 1781 | calculating one. |
1804 | 1837 | :param _mover: Supply an alternate FileMover, for testing | 1782 | :param _mover: Supply an alternate FileMover, for testing |
1805 | 1838 | """ | 1783 | """ |
2549 | 1839 | for hook in MutableTree.hooks['pre_transform']: | 1784 | raise NotImplementedError(self.apply) |
1807 | 1840 | hook(self._tree, self) | ||
1808 | 1841 | if not no_conflicts: | ||
1809 | 1842 | self._check_malformed() | ||
1810 | 1843 | with ui.ui_factory.nested_progress_bar() as child_pb: | ||
1811 | 1844 | if precomputed_delta is None: | ||
1812 | 1845 | child_pb.update(gettext('Apply phase'), 0, 2) | ||
1813 | 1846 | inventory_delta = self._generate_inventory_delta() | ||
1814 | 1847 | offset = 1 | ||
1815 | 1848 | else: | ||
1816 | 1849 | inventory_delta = precomputed_delta | ||
1817 | 1850 | offset = 0 | ||
1818 | 1851 | if _mover is None: | ||
1819 | 1852 | mover = _FileMover() | ||
1820 | 1853 | else: | ||
1821 | 1854 | mover = _mover | ||
1822 | 1855 | try: | ||
1823 | 1856 | child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset) | ||
1824 | 1857 | self._apply_removals(mover) | ||
1825 | 1858 | child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset) | ||
1826 | 1859 | modified_paths = self._apply_insertions(mover) | ||
1827 | 1860 | except BaseException: | ||
1828 | 1861 | mover.rollback() | ||
1829 | 1862 | raise | ||
1830 | 1863 | else: | ||
1831 | 1864 | mover.apply_deletions() | ||
1832 | 1865 | if not self.final_is_versioned(self.root): | ||
1833 | 1866 | inventory_delta = [e for e in inventory_delta if e[0] != ''] | ||
1834 | 1867 | self._tree.apply_inventory_delta(inventory_delta) | ||
1835 | 1868 | self._apply_observed_sha1s() | ||
1836 | 1869 | self._done = True | ||
1837 | 1870 | self.finalize() | ||
1838 | 1871 | return _TransformResults(modified_paths, self.rename_count) | ||
1839 | 1872 | |||
1840 | 1873 | def _generate_inventory_delta(self): | ||
1841 | 1874 | """Generate an inventory delta for the current transform.""" | ||
1842 | 1875 | inventory_delta = [] | ||
1843 | 1876 | new_paths = self._inventory_altered() | ||
1844 | 1877 | total_entries = len(new_paths) + len(self._removed_id) | ||
1845 | 1878 | with ui.ui_factory.nested_progress_bar() as child_pb: | ||
1846 | 1879 | for num, trans_id in enumerate(self._removed_id): | ||
1847 | 1880 | if (num % 10) == 0: | ||
1848 | 1881 | child_pb.update(gettext('removing file'), | ||
1849 | 1882 | num, total_entries) | ||
1850 | 1883 | if trans_id == self._new_root: | ||
1851 | 1884 | file_id = self._tree.path2id('') | ||
1852 | 1885 | else: | ||
1853 | 1886 | file_id = self.tree_file_id(trans_id) | ||
1854 | 1887 | # File-id isn't really being deleted, just moved | ||
1855 | 1888 | if file_id in self._r_new_id: | ||
1856 | 1889 | continue | ||
1857 | 1890 | path = self._tree_id_paths[trans_id] | ||
1858 | 1891 | inventory_delta.append((path, None, file_id, None)) | ||
1859 | 1892 | new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in | ||
1860 | 1893 | new_paths) | ||
1861 | 1894 | for num, (path, trans_id) in enumerate(new_paths): | ||
1862 | 1895 | if (num % 10) == 0: | ||
1863 | 1896 | child_pb.update(gettext('adding file'), | ||
1864 | 1897 | num + len(self._removed_id), total_entries) | ||
1865 | 1898 | file_id = new_path_file_ids[trans_id] | ||
1866 | 1899 | if file_id is None: | ||
1867 | 1900 | continue | ||
1868 | 1901 | kind = self.final_kind(trans_id) | ||
1869 | 1902 | if kind is None: | ||
1870 | 1903 | kind = self._tree.stored_kind(self._tree.id2path(file_id)) | ||
1871 | 1904 | parent_trans_id = self.final_parent(trans_id) | ||
1872 | 1905 | parent_file_id = new_path_file_ids.get(parent_trans_id) | ||
1873 | 1906 | if parent_file_id is None: | ||
1874 | 1907 | parent_file_id = self.final_file_id(parent_trans_id) | ||
1875 | 1908 | if trans_id in self._new_reference_revision: | ||
1876 | 1909 | new_entry = inventory.TreeReference( | ||
1877 | 1910 | file_id, | ||
1878 | 1911 | self._new_name[trans_id], | ||
1879 | 1912 | self.final_file_id(self._new_parent[trans_id]), | ||
1880 | 1913 | None, self._new_reference_revision[trans_id]) | ||
1881 | 1914 | else: | ||
1882 | 1915 | new_entry = inventory.make_entry(kind, | ||
1883 | 1916 | self.final_name(trans_id), | ||
1884 | 1917 | parent_file_id, file_id) | ||
1885 | 1918 | try: | ||
1886 | 1919 | old_path = self._tree.id2path(new_entry.file_id) | ||
1887 | 1920 | except errors.NoSuchId: | ||
1888 | 1921 | old_path = None | ||
1889 | 1922 | new_executability = self._new_executability.get(trans_id) | ||
1890 | 1923 | if new_executability is not None: | ||
1891 | 1924 | new_entry.executable = new_executability | ||
1892 | 1925 | inventory_delta.append( | ||
1893 | 1926 | (old_path, path, new_entry.file_id, new_entry)) | ||
1894 | 1927 | return inventory_delta | ||
1895 | 1928 | |||
1896 | 1929 | def _apply_removals(self, mover): | ||
1897 | 1930 | """Perform tree operations that remove directory/inventory names. | ||
1898 | 1931 | |||
1899 | 1932 | That is, delete files that are to be deleted, and put any files that | ||
1900 | 1933 | need renaming into limbo. This must be done in strict child-to-parent | ||
1901 | 1934 | order. | ||
1902 | 1935 | |||
1903 | 1936 | If inventory_delta is None, no inventory delta generation is performed. | ||
1904 | 1937 | """ | ||
1905 | 1938 | tree_paths = sorted(viewitems(self._tree_path_ids), reverse=True) | ||
1906 | 1939 | with ui.ui_factory.nested_progress_bar() as child_pb: | ||
1907 | 1940 | for num, (path, trans_id) in enumerate(tree_paths): | ||
1908 | 1941 | # do not attempt to move root into a subdirectory of itself. | ||
1909 | 1942 | if path == '': | ||
1910 | 1943 | continue | ||
1911 | 1944 | child_pb.update(gettext('removing file'), num, len(tree_paths)) | ||
1912 | 1945 | full_path = self._tree.abspath(path) | ||
1913 | 1946 | if trans_id in self._removed_contents: | ||
1914 | 1947 | delete_path = os.path.join(self._deletiondir, trans_id) | ||
1915 | 1948 | mover.pre_delete(full_path, delete_path) | ||
1916 | 1949 | elif (trans_id in self._new_name or | ||
1917 | 1950 | trans_id in self._new_parent): | ||
1918 | 1951 | try: | ||
1919 | 1952 | mover.rename(full_path, self._limbo_name(trans_id)) | ||
1920 | 1953 | except errors.TransformRenameFailed as e: | ||
1921 | 1954 | if e.errno != errno.ENOENT: | ||
1922 | 1955 | raise | ||
1923 | 1956 | else: | ||
1924 | 1957 | self.rename_count += 1 | ||
1925 | 1958 | |||
1926 | 1959 | def _apply_insertions(self, mover): | ||
1927 | 1960 | """Perform tree operations that insert directory/inventory names. | ||
1928 | 1961 | |||
1929 | 1962 | That is, create any files that need to be created, and restore from | ||
1930 | 1963 | limbo any files that needed renaming. This must be done in strict | ||
1931 | 1964 | parent-to-child order. | ||
1932 | 1965 | |||
1933 | 1966 | If inventory_delta is None, no inventory delta is calculated, and | ||
1934 | 1967 | no list of modified paths is returned. | ||
1935 | 1968 | """ | ||
1936 | 1969 | new_paths = self.new_paths(filesystem_only=True) | ||
1937 | 1970 | modified_paths = [] | ||
1938 | 1971 | with ui.ui_factory.nested_progress_bar() as child_pb: | ||
1939 | 1972 | for num, (path, trans_id) in enumerate(new_paths): | ||
1940 | 1973 | if (num % 10) == 0: | ||
1941 | 1974 | child_pb.update(gettext('adding file'), | ||
1942 | 1975 | num, len(new_paths)) | ||
1943 | 1976 | full_path = self._tree.abspath(path) | ||
1944 | 1977 | if trans_id in self._needs_rename: | ||
1945 | 1978 | try: | ||
1946 | 1979 | mover.rename(self._limbo_name(trans_id), full_path) | ||
1947 | 1980 | except errors.TransformRenameFailed as e: | ||
1948 | 1981 | # We may be renaming a dangling inventory id | ||
1949 | 1982 | if e.errno != errno.ENOENT: | ||
1950 | 1983 | raise | ||
1951 | 1984 | else: | ||
1952 | 1985 | self.rename_count += 1 | ||
1953 | 1986 | # TODO: if trans_id in self._observed_sha1s, we should | ||
1954 | 1987 | # re-stat the final target, since ctime will be | ||
1955 | 1988 | # updated by the change. | ||
1956 | 1989 | if (trans_id in self._new_contents | ||
1957 | 1990 | or self.path_changed(trans_id)): | ||
1958 | 1991 | if trans_id in self._new_contents: | ||
1959 | 1992 | modified_paths.append(full_path) | ||
1960 | 1993 | if trans_id in self._new_executability: | ||
1961 | 1994 | self._set_executability(path, trans_id) | ||
1962 | 1995 | if trans_id in self._observed_sha1s: | ||
1963 | 1996 | o_sha1, o_st_val = self._observed_sha1s[trans_id] | ||
1964 | 1997 | st = osutils.lstat(full_path) | ||
1965 | 1998 | self._observed_sha1s[trans_id] = (o_sha1, st) | ||
1966 | 1999 | for path, trans_id in new_paths: | ||
1967 | 2000 | # new_paths includes stuff like workingtree conflicts. Only the | ||
1968 | 2001 | # stuff in new_contents actually comes from limbo. | ||
1969 | 2002 | if trans_id in self._limbo_files: | ||
1970 | 2003 | del self._limbo_files[trans_id] | ||
1971 | 2004 | self._new_contents.clear() | ||
1972 | 2005 | return modified_paths | ||
1973 | 2006 | |||
1974 | 2007 | def _apply_observed_sha1s(self): | ||
1975 | 2008 | """After we have finished renaming everything, update observed sha1s | ||
1976 | 2009 | |||
1977 | 2010 | This has to be done after self._tree.apply_inventory_delta, otherwise | ||
1978 | 2011 | it doesn't know anything about the files we are updating. Also, we want | ||
1979 | 2012 | to do this as late as possible, so that most entries end up cached. | ||
1980 | 2013 | """ | ||
1981 | 2014 | # TODO: this doesn't update the stat information for directories. So | ||
1982 | 2015 | # the first 'bzr status' will still need to rewrite | ||
1983 | 2016 | # .bzr/checkout/dirstate. However, we at least don't need to | ||
1984 | 2017 | # re-read all of the files. | ||
1985 | 2018 | # TODO: If the operation took a while, we could do a time.sleep(3) here | ||
1986 | 2019 | # to allow the clock to tick over and ensure we won't have any | ||
1987 | 2020 | # problems. (we could observe start time, and finish time, and if | ||
1988 | 2021 | # it is less than eg 10% overhead, add a sleep call.) | ||
1989 | 2022 | paths = FinalPaths(self) | ||
1990 | 2023 | for trans_id, observed in viewitems(self._observed_sha1s): | ||
1991 | 2024 | path = paths.get_path(trans_id) | ||
1992 | 2025 | self._tree._observed_sha1(path, observed) | ||
1993 | 2026 | |||
1994 | 2027 | |||
1995 | 2028 | class TransformPreview(DiskTreeTransform): | ||
1996 | 2029 | """A TreeTransform for generating preview trees. | ||
1997 | 2030 | |||
1998 | 2031 | Unlike TreeTransform, this version works when the input tree is a | ||
1999 | 2032 | RevisionTree, rather than a WorkingTree. As a result, it tends to ignore | ||
2000 | 2033 | unversioned files in the input tree. | ||
2001 | 2034 | """ | ||
2002 | 2035 | |||
2003 | 2036 | def __init__(self, tree, pb=None, case_sensitive=True): | ||
2004 | 2037 | tree.lock_read() | ||
2005 | 2038 | limbodir = osutils.mkdtemp(prefix='bzr-limbo-') | ||
2006 | 2039 | DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive) | ||
2007 | 2040 | |||
2008 | 2041 | def tree_kind(self, trans_id): | ||
2009 | 2042 | path = self._tree_id_paths.get(trans_id) | ||
2010 | 2043 | if path is None: | ||
2011 | 2044 | return None | ||
2012 | 2045 | kind = self._tree.path_content_summary(path)[0] | ||
2013 | 2046 | if kind == 'missing': | ||
2014 | 2047 | kind = None | ||
2015 | 2048 | return kind | ||
2016 | 2049 | |||
2017 | 2050 | def _set_mode(self, trans_id, mode_id, typefunc): | ||
2018 | 2051 | """Set the mode of new file contents. | ||
2019 | 2052 | The mode_id is the existing file to get the mode from (often the same | ||
2020 | 2053 | as trans_id). The operation is only performed if there's a mode match | ||
2021 | 2054 | according to typefunc. | ||
2022 | 2055 | """ | ||
2023 | 2056 | # is it ok to ignore this? probably | ||
2024 | 2057 | pass | ||
2025 | 2058 | |||
2026 | 2059 | def iter_tree_children(self, parent_id): | ||
2027 | 2060 | """Iterate through the entry's tree children, if any""" | ||
2028 | 2061 | try: | ||
2029 | 2062 | path = self._tree_id_paths[parent_id] | ||
2030 | 2063 | except KeyError: | ||
2031 | 2064 | return | ||
2032 | 2065 | try: | ||
2033 | 2066 | entry = next(self._tree.iter_entries_by_dir( | ||
2034 | 2067 | specific_files=[path]))[1] | ||
2035 | 2068 | except StopIteration: | ||
2036 | 2069 | return | ||
2037 | 2070 | children = getattr(entry, 'children', {}) | ||
2038 | 2071 | for child in children: | ||
2039 | 2072 | childpath = joinpath(path, child) | ||
2040 | 2073 | yield self.trans_id_tree_path(childpath) | ||
2041 | 2074 | |||
2042 | 2075 | def new_orphan(self, trans_id, parent_id): | ||
2043 | 2076 | raise NotImplementedError(self.new_orphan) | ||
2044 | 2077 | |||
2045 | 2078 | |||
2046 | 2079 | class _PreviewTree(inventorytree.InventoryTree): | ||
2047 | 2080 | """Partial implementation of Tree to support show_diff_trees""" | ||
2048 | 2081 | |||
2049 | 2082 | def __init__(self, transform): | ||
2050 | 2083 | self._transform = transform | ||
2051 | 2084 | self._final_paths = FinalPaths(transform) | ||
2052 | 2085 | self.__by_parent = None | ||
2053 | 2086 | self._parent_ids = [] | ||
2054 | 2087 | self._all_children_cache = {} | ||
2055 | 2088 | self._path2trans_id_cache = {} | ||
2056 | 2089 | self._final_name_cache = {} | ||
2057 | 2090 | self._iter_changes_cache = dict((c.file_id, c) for c in | ||
2058 | 2091 | self._transform.iter_changes()) | ||
2059 | 2092 | |||
2060 | 2093 | def supports_tree_reference(self): | ||
2061 | 2094 | # TODO(jelmer): Support tree references in _PreviewTree. | ||
2062 | 2095 | # return self._transform._tree.supports_tree_reference() | ||
2063 | 2096 | return False | ||
2064 | 2097 | |||
2065 | 2098 | def _content_change(self, file_id): | ||
2066 | 2099 | """Return True if the content of this file changed""" | ||
2067 | 2100 | changes = self._iter_changes_cache.get(file_id) | ||
2068 | 2101 | return (changes is not None and changes.changed_content) | ||
2069 | 2102 | |||
2070 | 2103 | def _get_repository(self): | ||
2071 | 2104 | repo = getattr(self._transform._tree, '_repository', None) | ||
2072 | 2105 | if repo is None: | ||
2073 | 2106 | repo = self._transform._tree.branch.repository | ||
2074 | 2107 | return repo | ||
2075 | 2108 | |||
2076 | 2109 | def _iter_parent_trees(self): | ||
2077 | 2110 | for revision_id in self.get_parent_ids(): | ||
2078 | 2111 | try: | ||
2079 | 2112 | yield self.revision_tree(revision_id) | ||
2080 | 2113 | except errors.NoSuchRevisionInTree: | ||
2081 | 2114 | yield self._get_repository().revision_tree(revision_id) | ||
2082 | 2115 | |||
2083 | 2116 | def _get_file_revision(self, path, file_id, vf, tree_revision): | ||
2084 | 2117 | parent_keys = [ | ||
2085 | 2118 | (file_id, t.get_file_revision(t.id2path(file_id))) | ||
2086 | 2119 | for t in self._iter_parent_trees()] | ||
2087 | 2120 | vf.add_lines((file_id, tree_revision), parent_keys, | ||
2088 | 2121 | self.get_file_lines(path)) | ||
2089 | 2122 | repo = self._get_repository() | ||
2090 | 2123 | base_vf = repo.texts | ||
2091 | 2124 | if base_vf not in vf.fallback_versionedfiles: | ||
2092 | 2125 | vf.fallback_versionedfiles.append(base_vf) | ||
2093 | 2126 | return tree_revision | ||
2094 | 2127 | |||
2095 | 2128 | def _stat_limbo_file(self, trans_id): | ||
2096 | 2129 | name = self._transform._limbo_name(trans_id) | ||
2097 | 2130 | return os.lstat(name) | ||
2098 | 2131 | |||
2099 | 2132 | @property | ||
2100 | 2133 | def _by_parent(self): | ||
2101 | 2134 | if self.__by_parent is None: | ||
2102 | 2135 | self.__by_parent = self._transform.by_parent() | ||
2103 | 2136 | return self.__by_parent | ||
2104 | 2137 | |||
2105 | 2138 | def _comparison_data(self, entry, path): | ||
2106 | 2139 | kind, size, executable, link_or_sha1 = self.path_content_summary(path) | ||
2107 | 2140 | if kind == 'missing': | ||
2108 | 2141 | kind = None | ||
2109 | 2142 | executable = False | ||
2110 | 2143 | else: | ||
2111 | 2144 | executable = self.is_executable(path) | ||
2112 | 2145 | return kind, executable, None | ||
2113 | 2146 | |||
2114 | 2147 | def is_locked(self): | ||
2115 | 2148 | return False | ||
2116 | 2149 | |||
2117 | 2150 | def lock_read(self): | ||
2118 | 2151 | # Perhaps in theory, this should lock the TreeTransform? | ||
2119 | 2152 | return lock.LogicalLockResult(self.unlock) | ||
2120 | 2153 | |||
2121 | 2154 | def unlock(self): | ||
2122 | 2155 | pass | ||
2123 | 2156 | |||
2124 | 2157 | @property | ||
2125 | 2158 | def root_inventory(self): | ||
2126 | 2159 | """This Tree does not use inventory as its backing data.""" | ||
2127 | 2160 | raise NotImplementedError(_PreviewTree.root_inventory) | ||
2128 | 2161 | |||
2129 | 2162 | def all_file_ids(self): | ||
2130 | 2163 | tree_ids = set(self._transform._tree.all_file_ids()) | ||
2131 | 2164 | tree_ids.difference_update(self._transform.tree_file_id(t) | ||
2132 | 2165 | for t in self._transform._removed_id) | ||
2133 | 2166 | tree_ids.update(viewvalues(self._transform._new_id)) | ||
2134 | 2167 | return tree_ids | ||
2135 | 2168 | |||
2136 | 2169 | def all_versioned_paths(self): | ||
2137 | 2170 | tree_paths = set(self._transform._tree.all_versioned_paths()) | ||
2138 | 2171 | |||
2139 | 2172 | tree_paths.difference_update( | ||
2140 | 2173 | self._transform.trans_id_tree_path(t) | ||
2141 | 2174 | for t in self._transform._removed_id) | ||
2142 | 2175 | |||
2143 | 2176 | tree_paths.update( | ||
2144 | 2177 | self._final_paths._determine_path(t) | ||
2145 | 2178 | for t in self._transform._new_id) | ||
2146 | 2179 | |||
2147 | 2180 | return tree_paths | ||
2148 | 2181 | |||
2149 | 2182 | def _path2trans_id(self, path): | ||
2150 | 2183 | # We must not use None here, because that is a valid value to store. | ||
2151 | 2184 | trans_id = self._path2trans_id_cache.get(path, object) | ||
2152 | 2185 | if trans_id is not object: | ||
2153 | 2186 | return trans_id | ||
2154 | 2187 | segments = splitpath(path) | ||
2155 | 2188 | cur_parent = self._transform.root | ||
2156 | 2189 | for cur_segment in segments: | ||
2157 | 2190 | for child in self._all_children(cur_parent): | ||
2158 | 2191 | final_name = self._final_name_cache.get(child) | ||
2159 | 2192 | if final_name is None: | ||
2160 | 2193 | final_name = self._transform.final_name(child) | ||
2161 | 2194 | self._final_name_cache[child] = final_name | ||
2162 | 2195 | if final_name == cur_segment: | ||
2163 | 2196 | cur_parent = child | ||
2164 | 2197 | break | ||
2165 | 2198 | else: | ||
2166 | 2199 | self._path2trans_id_cache[path] = None | ||
2167 | 2200 | return None | ||
2168 | 2201 | self._path2trans_id_cache[path] = cur_parent | ||
2169 | 2202 | return cur_parent | ||
2170 | 2203 | |||
2171 | 2204 | def path2id(self, path): | ||
2172 | 2205 | if isinstance(path, list): | ||
2173 | 2206 | if path == []: | ||
2174 | 2207 | path = [""] | ||
2175 | 2208 | path = osutils.pathjoin(*path) | ||
2176 | 2209 | return self._transform.final_file_id(self._path2trans_id(path)) | ||
2177 | 2210 | |||
2178 | 2211 | def id2path(self, file_id, recurse='down'): | ||
2179 | 2212 | trans_id = self._transform.trans_id_file_id(file_id) | ||
2180 | 2213 | try: | ||
2181 | 2214 | return self._final_paths._determine_path(trans_id) | ||
2182 | 2215 | except NoFinalPath: | ||
2183 | 2216 | raise errors.NoSuchId(self, file_id) | ||
2184 | 2217 | |||
2185 | 2218 | def _all_children(self, trans_id): | ||
2186 | 2219 | children = self._all_children_cache.get(trans_id) | ||
2187 | 2220 | if children is not None: | ||
2188 | 2221 | return children | ||
2189 | 2222 | children = set(self._transform.iter_tree_children(trans_id)) | ||
2190 | 2223 | # children in the _new_parent set are provided by _by_parent. | ||
2191 | 2224 | children.difference_update(self._transform._new_parent) | ||
2192 | 2225 | children.update(self._by_parent.get(trans_id, [])) | ||
2193 | 2226 | self._all_children_cache[trans_id] = children | ||
2194 | 2227 | return children | ||
2195 | 2228 | |||
2196 | 2229 | def extras(self): | ||
2197 | 2230 | possible_extras = set(self._transform.trans_id_tree_path(p) for p | ||
2198 | 2231 | in self._transform._tree.extras()) | ||
2199 | 2232 | possible_extras.update(self._transform._new_contents) | ||
2200 | 2233 | possible_extras.update(self._transform._removed_id) | ||
2201 | 2234 | for trans_id in possible_extras: | ||
2202 | 2235 | if not self._transform.final_is_versioned(trans_id): | ||
2203 | 2236 | yield self._final_paths._determine_path(trans_id) | ||
2204 | 2237 | |||
2205 | 2238 | def _make_inv_entries(self, ordered_entries, specific_files=None): | ||
2206 | 2239 | for trans_id, parent_file_id in ordered_entries: | ||
2207 | 2240 | file_id = self._transform.final_file_id(trans_id) | ||
2208 | 2241 | if file_id is None: | ||
2209 | 2242 | continue | ||
2210 | 2243 | if (specific_files is not None | ||
2211 | 2244 | and self._final_paths.get_path(trans_id) not in specific_files): | ||
2212 | 2245 | continue | ||
2213 | 2246 | kind = self._transform.final_kind(trans_id) | ||
2214 | 2247 | if kind is None: | ||
2215 | 2248 | kind = self._transform._tree.stored_kind( | ||
2216 | 2249 | self._transform._tree.id2path(file_id)) | ||
2217 | 2250 | new_entry = inventory.make_entry( | ||
2218 | 2251 | kind, | ||
2219 | 2252 | self._transform.final_name(trans_id), | ||
2220 | 2253 | parent_file_id, file_id) | ||
2221 | 2254 | yield new_entry, trans_id | ||
2222 | 2255 | |||
2223 | 2256 | def _list_files_by_dir(self): | ||
2224 | 2257 | todo = [ROOT_PARENT] | ||
2225 | 2258 | ordered_ids = [] | ||
2226 | 2259 | while len(todo) > 0: | ||
2227 | 2260 | parent = todo.pop() | ||
2228 | 2261 | parent_file_id = self._transform.final_file_id(parent) | ||
2229 | 2262 | children = list(self._all_children(parent)) | ||
2230 | 2263 | paths = dict(zip(children, self._final_paths.get_paths(children))) | ||
2231 | 2264 | children.sort(key=paths.get) | ||
2232 | 2265 | todo.extend(reversed(children)) | ||
2233 | 2266 | for trans_id in children: | ||
2234 | 2267 | ordered_ids.append((trans_id, parent_file_id)) | ||
2235 | 2268 | return ordered_ids | ||
2236 | 2269 | |||
2237 | 2270 | def iter_child_entries(self, path): | ||
2238 | 2271 | trans_id = self._path2trans_id(path) | ||
2239 | 2272 | if trans_id is None: | ||
2240 | 2273 | raise errors.NoSuchFile(path) | ||
2241 | 2274 | todo = [(child_trans_id, trans_id) for child_trans_id in | ||
2242 | 2275 | self._all_children(trans_id)] | ||
2243 | 2276 | for entry, trans_id in self._make_inv_entries(todo): | ||
2244 | 2277 | yield entry | ||
2245 | 2278 | |||
2246 | 2279 | def iter_entries_by_dir(self, specific_files=None, recurse_nested=False): | ||
2247 | 2280 | if recurse_nested: | ||
2248 | 2281 | raise NotImplementedError( | ||
2249 | 2282 | 'follow tree references not yet supported') | ||
2250 | 2283 | |||
2251 | 2284 | # This may not be a maximally efficient implementation, but it is | ||
2252 | 2285 | # reasonably straightforward. An implementation that grafts the | ||
2253 | 2286 | # TreeTransform changes onto the tree's iter_entries_by_dir results | ||
2254 | 2287 | # might be more efficient, but requires tricky inferences about stack | ||
2255 | 2288 | # position. | ||
2256 | 2289 | ordered_ids = self._list_files_by_dir() | ||
2257 | 2290 | for entry, trans_id in self._make_inv_entries(ordered_ids, | ||
2258 | 2291 | specific_files): | ||
2259 | 2292 | yield self._final_paths.get_path(trans_id), entry | ||
2260 | 2293 | |||
2261 | 2294 | def _iter_entries_for_dir(self, dir_path): | ||
2262 | 2295 | """Return path, entry for items in a directory without recursing down.""" | ||
2263 | 2296 | ordered_ids = [] | ||
2264 | 2297 | dir_trans_id = self._path2trans_id(dir_path) | ||
2265 | 2298 | dir_id = self._transform.final_file_id(dir_trans_id) | ||
2266 | 2299 | for child_trans_id in self._all_children(dir_trans_id): | ||
2267 | 2300 | ordered_ids.append((child_trans_id, dir_id)) | ||
2268 | 2301 | path_entries = [] | ||
2269 | 2302 | for entry, trans_id in self._make_inv_entries(ordered_ids): | ||
2270 | 2303 | path_entries.append((self._final_paths.get_path(trans_id), entry)) | ||
2271 | 2304 | path_entries.sort() | ||
2272 | 2305 | return path_entries | ||
2273 | 2306 | |||
2274 | 2307 | def list_files(self, include_root=False, from_dir=None, recursive=True, | ||
2275 | 2308 | recurse_nested=False): | ||
2276 | 2309 | """See WorkingTree.list_files.""" | ||
2277 | 2310 | if recurse_nested: | ||
2278 | 2311 | raise NotImplementedError( | ||
2279 | 2312 | 'follow tree references not yet supported') | ||
2280 | 2313 | |||
2281 | 2314 | # XXX This should behave like WorkingTree.list_files, but is really | ||
2282 | 2315 | # more like RevisionTree.list_files. | ||
2283 | 2316 | if from_dir == '.': | ||
2284 | 2317 | from_dir = None | ||
2285 | 2318 | if recursive: | ||
2286 | 2319 | prefix = None | ||
2287 | 2320 | if from_dir: | ||
2288 | 2321 | prefix = from_dir + '/' | ||
2289 | 2322 | entries = self.iter_entries_by_dir() | ||
2290 | 2323 | for path, entry in entries: | ||
2291 | 2324 | if entry.name == '' and not include_root: | ||
2292 | 2325 | continue | ||
2293 | 2326 | if prefix: | ||
2294 | 2327 | if not path.startswith(prefix): | ||
2295 | 2328 | continue | ||
2296 | 2329 | path = path[len(prefix):] | ||
2297 | 2330 | yield path, 'V', entry.kind, entry | ||
2298 | 2331 | else: | ||
2299 | 2332 | if from_dir is None and include_root is True: | ||
2300 | 2333 | root_entry = inventory.make_entry( | ||
2301 | 2334 | 'directory', '', ROOT_PARENT, self.path2id('')) | ||
2302 | 2335 | yield '', 'V', 'directory', root_entry | ||
2303 | 2336 | entries = self._iter_entries_for_dir(from_dir or '') | ||
2304 | 2337 | for path, entry in entries: | ||
2305 | 2338 | yield path, 'V', entry.kind, entry | ||
2306 | 2339 | |||
2307 | 2340 | def kind(self, path): | ||
2308 | 2341 | trans_id = self._path2trans_id(path) | ||
2309 | 2342 | if trans_id is None: | ||
2310 | 2343 | raise errors.NoSuchFile(path) | ||
2311 | 2344 | return self._transform.final_kind(trans_id) | ||
2312 | 2345 | |||
2313 | 2346 | def stored_kind(self, path): | ||
2314 | 2347 | trans_id = self._path2trans_id(path) | ||
2315 | 2348 | if trans_id is None: | ||
2316 | 2349 | raise errors.NoSuchFile(path) | ||
2317 | 2350 | try: | ||
2318 | 2351 | return self._transform._new_contents[trans_id] | ||
2319 | 2352 | except KeyError: | ||
2320 | 2353 | return self._transform._tree.stored_kind(path) | ||
2321 | 2354 | |||
2322 | 2355 | def get_file_mtime(self, path): | ||
2323 | 2356 | """See Tree.get_file_mtime""" | ||
2324 | 2357 | file_id = self.path2id(path) | ||
2325 | 2358 | if file_id is None: | ||
2326 | 2359 | raise errors.NoSuchFile(path) | ||
2327 | 2360 | if not self._content_change(file_id): | ||
2328 | 2361 | return self._transform._tree.get_file_mtime( | ||
2329 | 2362 | self._transform._tree.id2path(file_id)) | ||
2330 | 2363 | trans_id = self._path2trans_id(path) | ||
2331 | 2364 | return self._stat_limbo_file(trans_id).st_mtime | ||
2332 | 2365 | |||
2333 | 2366 | def get_file_size(self, path): | ||
2334 | 2367 | """See Tree.get_file_size""" | ||
2335 | 2368 | trans_id = self._path2trans_id(path) | ||
2336 | 2369 | if trans_id is None: | ||
2337 | 2370 | raise errors.NoSuchFile(path) | ||
2338 | 2371 | kind = self._transform.final_kind(trans_id) | ||
2339 | 2372 | if kind != 'file': | ||
2340 | 2373 | return None | ||
2341 | 2374 | if trans_id in self._transform._new_contents: | ||
2342 | 2375 | return self._stat_limbo_file(trans_id).st_size | ||
2343 | 2376 | if self.kind(path) == 'file': | ||
2344 | 2377 | return self._transform._tree.get_file_size(path) | ||
2345 | 2378 | else: | ||
2346 | 2379 | return None | ||
2347 | 2380 | |||
2348 | 2381 | def get_file_verifier(self, path, stat_value=None): | ||
2349 | 2382 | trans_id = self._path2trans_id(path) | ||
2350 | 2383 | if trans_id is None: | ||
2351 | 2384 | raise errors.NoSuchFile(path) | ||
2352 | 2385 | kind = self._transform._new_contents.get(trans_id) | ||
2353 | 2386 | if kind is None: | ||
2354 | 2387 | return self._transform._tree.get_file_verifier(path) | ||
2355 | 2388 | if kind == 'file': | ||
2356 | 2389 | with self.get_file(path) as fileobj: | ||
2357 | 2390 | return ("SHA1", sha_file(fileobj)) | ||
2358 | 2391 | |||
2359 | 2392 | def get_file_sha1(self, path, stat_value=None): | ||
2360 | 2393 | trans_id = self._path2trans_id(path) | ||
2361 | 2394 | if trans_id is None: | ||
2362 | 2395 | raise errors.NoSuchFile(path) | ||
2363 | 2396 | kind = self._transform._new_contents.get(trans_id) | ||
2364 | 2397 | if kind is None: | ||
2365 | 2398 | return self._transform._tree.get_file_sha1(path) | ||
2366 | 2399 | if kind == 'file': | ||
2367 | 2400 | with self.get_file(path) as fileobj: | ||
2368 | 2401 | return sha_file(fileobj) | ||
2369 | 2402 | |||
2370 | 2403 | def get_reference_revision(self, path): | ||
2371 | 2404 | trans_id = self._path2trans_id(path) | ||
2372 | 2405 | if trans_id is None: | ||
2373 | 2406 | raise errors.NoSuchFile(path) | ||
2374 | 2407 | reference_revision = self._transform._new_reference_revision.get(trans_id) | ||
2375 | 2408 | if reference_revision is None: | ||
2376 | 2409 | return self._transform._tree.get_reference_revision(path) | ||
2377 | 2410 | return reference_revision | ||
2378 | 2411 | |||
2379 | 2412 | def is_executable(self, path): | ||
2380 | 2413 | trans_id = self._path2trans_id(path) | ||
2381 | 2414 | if trans_id is None: | ||
2382 | 2415 | return False | ||
2383 | 2416 | try: | ||
2384 | 2417 | return self._transform._new_executability[trans_id] | ||
2385 | 2418 | except KeyError: | ||
2386 | 2419 | try: | ||
2387 | 2420 | return self._transform._tree.is_executable(path) | ||
2388 | 2421 | except OSError as e: | ||
2389 | 2422 | if e.errno == errno.ENOENT: | ||
2390 | 2423 | return False | ||
2391 | 2424 | raise | ||
2392 | 2425 | except errors.NoSuchFile: | ||
2393 | 2426 | return False | ||
2394 | 2427 | |||
2395 | 2428 | def has_filename(self, path): | ||
2396 | 2429 | trans_id = self._path2trans_id(path) | ||
2397 | 2430 | if trans_id in self._transform._new_contents: | ||
2398 | 2431 | return True | ||
2399 | 2432 | elif trans_id in self._transform._removed_contents: | ||
2400 | 2433 | return False | ||
2401 | 2434 | else: | ||
2402 | 2435 | return self._transform._tree.has_filename(path) | ||
2403 | 2436 | |||
2404 | 2437 | def path_content_summary(self, path): | ||
2405 | 2438 | trans_id = self._path2trans_id(path) | ||
2406 | 2439 | tt = self._transform | ||
2407 | 2440 | tree_path = tt._tree_id_paths.get(trans_id) | ||
2408 | 2441 | kind = tt._new_contents.get(trans_id) | ||
2409 | 2442 | if kind is None: | ||
2410 | 2443 | if tree_path is None or trans_id in tt._removed_contents: | ||
2411 | 2444 | return 'missing', None, None, None | ||
2412 | 2445 | summary = tt._tree.path_content_summary(tree_path) | ||
2413 | 2446 | kind, size, executable, link_or_sha1 = summary | ||
2414 | 2447 | else: | ||
2415 | 2448 | link_or_sha1 = None | ||
2416 | 2449 | limbo_name = tt._limbo_name(trans_id) | ||
2417 | 2450 | if trans_id in tt._new_reference_revision: | ||
2418 | 2451 | kind = 'tree-reference' | ||
2419 | 2452 | if kind == 'file': | ||
2420 | 2453 | statval = os.lstat(limbo_name) | ||
2421 | 2454 | size = statval.st_size | ||
2422 | 2455 | if not tt._limbo_supports_executable(): | ||
2423 | 2456 | executable = False | ||
2424 | 2457 | else: | ||
2425 | 2458 | executable = statval.st_mode & S_IEXEC | ||
2426 | 2459 | else: | ||
2427 | 2460 | size = None | ||
2428 | 2461 | executable = None | ||
2429 | 2462 | if kind == 'symlink': | ||
2430 | 2463 | link_or_sha1 = os.readlink(limbo_name) | ||
2431 | 2464 | if not isinstance(link_or_sha1, text_type): | ||
2432 | 2465 | link_or_sha1 = link_or_sha1.decode(osutils._fs_enc) | ||
2433 | 2466 | executable = tt._new_executability.get(trans_id, executable) | ||
2434 | 2467 | return kind, size, executable, link_or_sha1 | ||
2435 | 2468 | |||
2436 | 2469 | def iter_changes(self, from_tree, include_unchanged=False, | ||
2437 | 2470 | specific_files=None, pb=None, extra_trees=None, | ||
2438 | 2471 | require_versioned=True, want_unversioned=False): | ||
2439 | 2472 | """See InterTree.iter_changes. | ||
2440 | 2473 | |||
2441 | 2474 | This has a fast path that is only used when the from_tree matches | ||
2442 | 2475 | the transform tree, and no fancy options are supplied. | ||
2443 | 2476 | """ | ||
2444 | 2477 | if (from_tree is not self._transform._tree or include_unchanged | ||
2445 | 2478 | or specific_files or want_unversioned): | ||
2446 | 2479 | from .bzr.inventorytree import InterInventoryTree | ||
2447 | 2480 | return InterInventoryTree(from_tree, self).iter_changes( | ||
2448 | 2481 | include_unchanged=include_unchanged, | ||
2449 | 2482 | specific_files=specific_files, | ||
2450 | 2483 | pb=pb, | ||
2451 | 2484 | extra_trees=extra_trees, | ||
2452 | 2485 | require_versioned=require_versioned, | ||
2453 | 2486 | want_unversioned=want_unversioned) | ||
2454 | 2487 | if want_unversioned: | ||
2455 | 2488 | raise ValueError('want_unversioned is not supported') | ||
2456 | 2489 | return self._transform.iter_changes() | ||
2457 | 2490 | |||
2458 | 2491 | def get_file(self, path): | ||
2459 | 2492 | """See Tree.get_file""" | ||
2460 | 2493 | file_id = self.path2id(path) | ||
2461 | 2494 | if not self._content_change(file_id): | ||
2462 | 2495 | return self._transform._tree.get_file(path) | ||
2463 | 2496 | trans_id = self._path2trans_id(path) | ||
2464 | 2497 | name = self._transform._limbo_name(trans_id) | ||
2465 | 2498 | return open(name, 'rb') | ||
2466 | 2499 | |||
2467 | 2500 | def get_file_with_stat(self, path): | ||
2468 | 2501 | return self.get_file(path), None | ||
2469 | 2502 | |||
2470 | 2503 | def annotate_iter(self, path, | ||
2471 | 2504 | default_revision=_mod_revision.CURRENT_REVISION): | ||
2472 | 2505 | file_id = self.path2id(path) | ||
2473 | 2506 | changes = self._iter_changes_cache.get(file_id) | ||
2474 | 2507 | if changes is None: | ||
2475 | 2508 | get_old = True | ||
2476 | 2509 | else: | ||
2477 | 2510 | changed_content, versioned, kind = ( | ||
2478 | 2511 | changes.changed_content, changes.versioned, changes.kind) | ||
2479 | 2512 | if kind[1] is None: | ||
2480 | 2513 | return None | ||
2481 | 2514 | get_old = (kind[0] == 'file' and versioned[0]) | ||
2482 | 2515 | if get_old: | ||
2483 | 2516 | old_annotation = self._transform._tree.annotate_iter( | ||
2484 | 2517 | path, default_revision=default_revision) | ||
2485 | 2518 | else: | ||
2486 | 2519 | old_annotation = [] | ||
2487 | 2520 | if changes is None: | ||
2488 | 2521 | return old_annotation | ||
2489 | 2522 | if not changed_content: | ||
2490 | 2523 | return old_annotation | ||
2491 | 2524 | # TODO: This is doing something similar to what WT.annotate_iter is | ||
2492 | 2525 | # doing, however it fails slightly because it doesn't know what | ||
2493 | 2526 | # the *other* revision_id is, so it doesn't know how to give the | ||
2494 | 2527 | # other as the origin for some lines, they all get | ||
2495 | 2528 | # 'default_revision' | ||
2496 | 2529 | # It would be nice to be able to use the new Annotator based | ||
2497 | 2530 | # approach, as well. | ||
2498 | 2531 | return annotate.reannotate([old_annotation], | ||
2499 | 2532 | self.get_file(path).readlines(), | ||
2500 | 2533 | default_revision) | ||
2501 | 2534 | |||
2502 | 2535 | def get_symlink_target(self, path): | ||
2503 | 2536 | """See Tree.get_symlink_target""" | ||
2504 | 2537 | file_id = self.path2id(path) | ||
2505 | 2538 | if not self._content_change(file_id): | ||
2506 | 2539 | return self._transform._tree.get_symlink_target(path) | ||
2507 | 2540 | trans_id = self._path2trans_id(path) | ||
2508 | 2541 | name = self._transform._limbo_name(trans_id) | ||
2509 | 2542 | return osutils.readlink(name) | ||
2510 | 2543 | |||
2511 | 2544 | def walkdirs(self, prefix=''): | ||
2512 | 2545 | pending = [self._transform.root] | ||
2513 | 2546 | while len(pending) > 0: | ||
2514 | 2547 | parent_id = pending.pop() | ||
2515 | 2548 | children = [] | ||
2516 | 2549 | subdirs = [] | ||
2517 | 2550 | prefix = prefix.rstrip('/') | ||
2518 | 2551 | parent_path = self._final_paths.get_path(parent_id) | ||
2519 | 2552 | parent_file_id = self._transform.final_file_id(parent_id) | ||
2520 | 2553 | for child_id in self._all_children(parent_id): | ||
2521 | 2554 | path_from_root = self._final_paths.get_path(child_id) | ||
2522 | 2555 | basename = self._transform.final_name(child_id) | ||
2523 | 2556 | file_id = self._transform.final_file_id(child_id) | ||
2524 | 2557 | kind = self._transform.final_kind(child_id) | ||
2525 | 2558 | if kind is not None: | ||
2526 | 2559 | versioned_kind = kind | ||
2527 | 2560 | else: | ||
2528 | 2561 | kind = 'unknown' | ||
2529 | 2562 | versioned_kind = self._transform._tree.stored_kind( | ||
2530 | 2563 | self._transform._tree.id2path(file_id)) | ||
2531 | 2564 | if versioned_kind == 'directory': | ||
2532 | 2565 | subdirs.append(child_id) | ||
2533 | 2566 | children.append((path_from_root, basename, kind, None, | ||
2534 | 2567 | file_id, versioned_kind)) | ||
2535 | 2568 | children.sort() | ||
2536 | 2569 | if parent_path.startswith(prefix): | ||
2537 | 2570 | yield (parent_path, parent_file_id), children | ||
2538 | 2571 | pending.extend(sorted(subdirs, key=self._final_paths.get_path, | ||
2539 | 2572 | reverse=True)) | ||
2540 | 2573 | |||
2541 | 2574 | def get_parent_ids(self): | ||
2542 | 2575 | return self._parent_ids | ||
2543 | 2576 | |||
2544 | 2577 | def set_parent_ids(self, parent_ids): | ||
2545 | 2578 | self._parent_ids = parent_ids | ||
2546 | 2579 | |||
2547 | 2580 | def get_revision_tree(self, revision_id): | ||
2548 | 2581 | return self._transform._tree.get_revision_tree(revision_id) | ||
2550 | 2582 | 1785 | ||
2551 | 2583 | 1786 | ||
2552 | 2584 | def joinpath(parent, child): | 1787 | def joinpath(parent, child): |
2553 | @@ -3278,7 +2481,7 @@ | |||
2554 | 3278 | raise errors.FileExists(to, str(e)) | 2481 | raise errors.FileExists(to, str(e)) |
2555 | 3279 | # normal OSError doesn't include filenames so it's hard to see where | 2482 | # normal OSError doesn't include filenames so it's hard to see where |
2556 | 3280 | # the problem is, see https://bugs.launchpad.net/bzr/+bug/491763 | 2483 | # the problem is, see https://bugs.launchpad.net/bzr/+bug/491763 |
2558 | 3281 | raise errors.TransformRenameFailed(from_, to, str(e), e.errno) | 2484 | raise TransformRenameFailed(from_, to, str(e), e.errno) |
2559 | 3282 | self.past_renames.append((from_, to)) | 2485 | self.past_renames.append((from_, to)) |
2560 | 3283 | 2486 | ||
2561 | 3284 | def pre_delete(self, from_, to): | 2487 | def pre_delete(self, from_, to): |
2562 | @@ -3297,7 +2500,7 @@ | |||
2563 | 3297 | try: | 2500 | try: |
2564 | 3298 | os.rename(to, from_) | 2501 | os.rename(to, from_) |
2565 | 3299 | except OSError as e: | 2502 | except OSError as e: |
2567 | 3300 | raise errors.TransformRenameFailed(to, from_, str(e), e.errno) | 2503 | raise TransformRenameFailed(to, from_, str(e), e.errno) |
2568 | 3301 | # after rollback, don't reuse _FileMover | 2504 | # after rollback, don't reuse _FileMover |
2569 | 3302 | self.past_renames = None | 2505 | self.past_renames = None |
2570 | 3303 | self.pending_deletions = None | 2506 | self.pending_deletions = None |
Voting criteria not met /ci.breezy- vcs.org/ job/brz- 3.1/job/ brz-3.1- land/177/
https:/