Status: | Merged |
---|---|
Approved by: | Jelmer Vernooij |
Approved revision: | no longer in the source branch. |
Merge reported by: | The Breezy Bot |
Merged at revision: | not available |
Proposed branch: | lp:~jelmer/brz/transform |
Merge into: | lp:brz/3.1 |
Diff against target: |
2454 lines (+969/-692) 14 files modified
breezy/bzr/transform.py (+21/-11) breezy/git/commit.py (+3/-0) breezy/git/transform.py (+536/-425) breezy/git/tree.py (+62/-53) breezy/git/workingtree.py (+11/-22) breezy/tests/per_tree/test_transform.py (+47/-21) breezy/tests/per_workingtree/test_commit.py (+1/-1) breezy/tests/per_workingtree/test_merge_from_branch.py (+1/-1) breezy/tests/per_workingtree/test_smart_add.py (+3/-0) breezy/tests/per_workingtree/test_transform.py (+253/-148) breezy/tests/per_workingtree/test_unversion.py (+1/-1) breezy/tests/per_workingtree/test_workingtree.py (+14/-6) breezy/transform.py (+10/-1) breezy/workingtree.py (+6/-2) |
To merge this branch: | bzr merge lp:~jelmer/brz/transform |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jelmer Vernooij | Approve | ||
Review via email: mp+389623@code.launchpad.net |
Commit message
Update the GitTreeTransform to not use file ids internally.
Description of the change
Update the GitTreeTransform to not use file ids internally.
Git file ids are tied to the path, which can lead to all sorts of odd behaviour.
To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'breezy/bzr/transform.py' |
2 | --- breezy/bzr/transform.py 2020-08-15 14:57:23 +0000 |
3 | +++ breezy/bzr/transform.py 2020-08-20 19:27:58 +0000 |
4 | @@ -1913,6 +1913,9 @@ |
5 | self._iter_changes_cache = { |
6 | c.file_id: c for c in self._transform.iter_changes()} |
7 | |
8 | + def supports_setting_file_ids(self): |
9 | + return True |
10 | + |
11 | def supports_tree_reference(self): |
12 | # TODO(jelmer): Support tree references in PreviewTree. |
13 | # return self._transform._tree.supports_tree_reference() |
14 | @@ -2169,21 +2172,28 @@ |
15 | file_id = self.path2id(path) |
16 | changes = self._iter_changes_cache.get(file_id) |
17 | if changes is None: |
18 | - get_old = True |
19 | + if file_id is None: |
20 | + old_path = None |
21 | + else: |
22 | + old_path = self._transform._tree.id2path(file_id) |
23 | else: |
24 | - changed_content, versioned, kind = ( |
25 | - changes.changed_content, changes.versioned, changes.kind) |
26 | - if kind[1] is None: |
27 | + if changes.kind[1] is None: |
28 | return None |
29 | - get_old = (kind[0] == 'file' and versioned[0]) |
30 | - if get_old: |
31 | + if changes.kind[0] == 'file' and changes.versioned[0]: |
32 | + old_path = changes.path[0] |
33 | + else: |
34 | + old_path = None |
35 | + if old_path is not None: |
36 | old_annotation = self._transform._tree.annotate_iter( |
37 | - path, default_revision=default_revision) |
38 | + old_path, default_revision=default_revision) |
39 | else: |
40 | old_annotation = [] |
41 | if changes is None: |
42 | - return old_annotation |
43 | - if not changed_content: |
44 | + if old_path is None: |
45 | + return None |
46 | + else: |
47 | + return old_annotation |
48 | + if not changes.changed_content: |
49 | return old_annotation |
50 | # TODO: This is doing something similar to what WT.annotate_iter is |
51 | # doing, however it fails slightly because it doesn't know what |
52 | @@ -2193,7 +2203,7 @@ |
53 | # It would be nice to be able to use the new Annotator based |
54 | # approach, as well. |
55 | return annotate.reannotate([old_annotation], |
56 | - self.get_file(path).readlines(), |
57 | + self.get_file_lines(path), |
58 | default_revision) |
59 | |
60 | def walkdirs(self, prefix=''): |
61 | @@ -2215,7 +2225,7 @@ |
62 | else: |
63 | kind = 'unknown' |
64 | versioned_kind = self._transform._tree.stored_kind( |
65 | - self._transform._tree.id2path(file_id)) |
66 | + path_from_root) |
67 | if versioned_kind == 'directory': |
68 | subdirs.append(child_id) |
69 | children.append((path_from_root, basename, kind, None, |
70 | |
71 | === modified file 'breezy/git/commit.py' |
72 | --- breezy/git/commit.py 2020-08-14 19:54:16 +0000 |
73 | +++ breezy/git/commit.py 2020-08-20 19:27:58 +0000 |
74 | @@ -80,6 +80,9 @@ |
75 | def record_iter_changes(self, workingtree, basis_revid, iter_changes): |
76 | seen_root = False |
77 | for change in iter_changes: |
78 | + if change.kind == (None, None): |
79 | + # Ephemeral |
80 | + continue |
81 | if change.versioned[0] and not change.copied: |
82 | file_id = self._mapping.generate_file_id(change.path[0]) |
83 | elif change.versioned[1]: |
84 | |
85 | === modified file 'breezy/git/transform.py' |
86 | --- breezy/git/transform.py 2020-08-14 19:53:25 +0000 |
87 | +++ breezy/git/transform.py 2020-08-20 19:27:58 +0000 |
88 | @@ -19,14 +19,30 @@ |
89 | |
90 | import errno |
91 | import os |
92 | -from stat import S_ISREG |
93 | +import posixpath |
94 | +from stat import S_IEXEC, S_ISREG |
95 | import time |
96 | |
97 | -from .. import conflicts, errors, multiparent, osutils, trace, ui, urlutils |
98 | +from .mapping import encode_git_path, mode_kind, mode_is_executable, object_mode |
99 | +from .tree import GitTree, GitTreeDirectory, GitTreeSymlink, GitTreeFile |
100 | + |
101 | +from .. import ( |
102 | + annotate, |
103 | + conflicts, |
104 | + errors, |
105 | + multiparent, |
106 | + osutils, |
107 | + revision as _mod_revision, |
108 | + trace, |
109 | + ui, |
110 | + urlutils, |
111 | + ) |
112 | from ..i18n import gettext |
113 | from ..mutabletree import MutableTree |
114 | -from ..sixish import viewitems, viewvalues |
115 | +from ..tree import InterTree, TreeChange |
116 | +from ..sixish import text_type, viewitems, viewvalues |
117 | from ..transform import ( |
118 | + PreviewTree, |
119 | TreeTransform, |
120 | _TransformResults, |
121 | _FileMover, |
122 | @@ -39,10 +55,9 @@ |
123 | ReusingTransform, |
124 | MalformedTransform, |
125 | ) |
126 | -from ..bzr.inventorytree import InventoryTreeChange |
127 | |
128 | -from ..bzr import inventory |
129 | -from ..bzr.transform import TransformPreview as GitTransformPreview |
130 | +from dulwich.index import commit_tree, blob_from_path_and_stat |
131 | +from dulwich.objects import Blob |
132 | |
133 | |
134 | class TreeTransformBase(TreeTransform): |
135 | @@ -60,19 +75,17 @@ |
136 | super(TreeTransformBase, self).__init__(tree, pb=pb) |
137 | # mapping of trans_id => (sha1 of content, stat_value) |
138 | self._observed_sha1s = {} |
139 | - # Mapping of trans_id -> new file_id |
140 | - self._new_id = {} |
141 | - # Mapping of old file-id -> trans_id |
142 | - self._non_present_ids = {} |
143 | - # Mapping of new file_id -> trans_id |
144 | - self._r_new_id = {} |
145 | + # Set of versioned trans ids |
146 | + self._versioned = set() |
147 | # The trans_id that will be used as the tree root |
148 | - if tree.is_versioned(''): |
149 | - self._new_root = self.trans_id_tree_path('') |
150 | - else: |
151 | - self._new_root = None |
152 | + self.root = self.trans_id_tree_path('') |
153 | # Whether the target is case sensitive |
154 | self._case_sensitive_target = case_sensitive |
155 | + self._symlink_target = {} |
156 | + |
157 | + @property |
158 | + def mapping(self): |
159 | + return self._tree.mapping |
160 | |
161 | def finalize(self): |
162 | """Release the working tree lock, if held. |
163 | @@ -87,11 +100,6 @@ |
164 | self._tree.unlock() |
165 | self._tree = None |
166 | |
167 | - def __get_root(self): |
168 | - return self._new_root |
169 | - |
170 | - root = property(__get_root) |
171 | - |
172 | def create_path(self, name, parent): |
173 | """Assign a transaction id to a new path""" |
174 | trans_id = self.assign_id() |
175 | @@ -101,35 +109,7 @@ |
176 | |
177 | def adjust_root_path(self, name, parent): |
178 | """Emulate moving the root by moving all children, instead. |
179 | - |
180 | - We do this by undoing the association of root's transaction id with the |
181 | - current tree. This allows us to create a new directory with that |
182 | - transaction id. We unversion the root directory and version the |
183 | - physically new directory, and hope someone versions the tree root |
184 | - later. |
185 | """ |
186 | - old_root = self._new_root |
187 | - old_root_file_id = self.final_file_id(old_root) |
188 | - # force moving all children of root |
189 | - for child_id in self.iter_tree_children(old_root): |
190 | - if child_id != parent: |
191 | - self.adjust_path(self.final_name(child_id), |
192 | - self.final_parent(child_id), child_id) |
193 | - file_id = self.final_file_id(child_id) |
194 | - if file_id is not None: |
195 | - self.unversion_file(child_id) |
196 | - self.version_file(child_id, file_id=file_id) |
197 | - |
198 | - # the physical root needs a new transaction id |
199 | - self._tree_path_ids.pop("") |
200 | - self._tree_id_paths.pop(old_root) |
201 | - self._new_root = self.trans_id_tree_path('') |
202 | - if parent == old_root: |
203 | - parent = self._new_root |
204 | - self.adjust_path(name, parent, old_root) |
205 | - self.create_directory(old_root) |
206 | - self.version_file(old_root, file_id=old_root_file_id) |
207 | - self.unversion_file(self._new_root) |
208 | |
209 | def fixup_new_roots(self): |
210 | """Reinterpret requests to change the root directory |
211 | @@ -149,26 +129,12 @@ |
212 | return |
213 | if len(new_roots) != 1: |
214 | raise ValueError('A tree cannot have two roots!') |
215 | - if self._new_root is None: |
216 | - self._new_root = new_roots[0] |
217 | - return |
218 | old_new_root = new_roots[0] |
219 | # unversion the new root's directory. |
220 | - if self.final_kind(self._new_root) is None: |
221 | - file_id = self.final_file_id(old_new_root) |
222 | - else: |
223 | - file_id = self.final_file_id(self._new_root) |
224 | - if old_new_root in self._new_id: |
225 | + if old_new_root in self._versioned: |
226 | self.cancel_versioning(old_new_root) |
227 | else: |
228 | self.unversion_file(old_new_root) |
229 | - # if, at this stage, root still has an old file_id, zap it so we can |
230 | - # stick a new one in. |
231 | - if (self.tree_file_id(self._new_root) is not None |
232 | - and self._new_root not in self._removed_id): |
233 | - self.unversion_file(self._new_root) |
234 | - if file_id is not None: |
235 | - self.version_file(self._new_root, file_id=file_id) |
236 | |
237 | # Now move children of new root into old root directory. |
238 | # Ensure all children are registered with the transaction, but don't |
239 | @@ -176,7 +142,7 @@ |
240 | list(self.iter_tree_children(old_new_root)) |
241 | # Move all children of new root into old root directory. |
242 | for child in self.by_parent().get(old_new_root, []): |
243 | - self.adjust_path(self.final_name(child), self._new_root, child) |
244 | + self.adjust_path(self.final_name(child), self.root, child) |
245 | |
246 | # Ensure old_new_root has no directory. |
247 | if old_new_root in self._new_contents: |
248 | @@ -185,8 +151,8 @@ |
249 | self.delete_contents(old_new_root) |
250 | |
251 | # prevent deletion of root directory. |
252 | - if self._new_root in self._removed_contents: |
253 | - self.cancel_deletion(self._new_root) |
254 | + if self.root in self._removed_contents: |
255 | + self.cancel_deletion(self.root) |
256 | |
257 | # destroy path info for old_new_root. |
258 | del self._new_parent[old_new_root] |
259 | @@ -200,24 +166,14 @@ |
260 | """ |
261 | if file_id is None: |
262 | raise ValueError('None is not a valid file id') |
263 | - if file_id in self._r_new_id and self._r_new_id[file_id] is not None: |
264 | - return self._r_new_id[file_id] |
265 | - else: |
266 | - try: |
267 | - path = self._tree.id2path(file_id) |
268 | - except errors.NoSuchId: |
269 | - if file_id in self._non_present_ids: |
270 | - return self._non_present_ids[file_id] |
271 | - else: |
272 | - trans_id = self.assign_id() |
273 | - self._non_present_ids[file_id] = trans_id |
274 | - return trans_id |
275 | - else: |
276 | - return self.trans_id_tree_path(path) |
277 | + path = self.mapping.parse_file_id(file_id) |
278 | + return self.trans_id_tree_path(path) |
279 | |
280 | def version_file(self, trans_id, file_id=None): |
281 | """Schedule a file to become versioned.""" |
282 | - raise NotImplementedError(self.version_file) |
283 | + if trans_id in self._versioned: |
284 | + raise errors.DuplicateKey(key=trans_id) |
285 | + self._versioned.add(trans_id) |
286 | |
287 | def cancel_versioning(self, trans_id): |
288 | """Undo a previous versioning of a file""" |
289 | @@ -234,53 +190,25 @@ |
290 | stale_ids = self._needs_rename.difference(self._new_name) |
291 | stale_ids.difference_update(self._new_parent) |
292 | stale_ids.difference_update(self._new_contents) |
293 | - stale_ids.difference_update(self._new_id) |
294 | + stale_ids.difference_update(self._versioned) |
295 | needs_rename = self._needs_rename.difference(stale_ids) |
296 | id_sets = (needs_rename, self._new_executability) |
297 | else: |
298 | id_sets = (self._new_name, self._new_parent, self._new_contents, |
299 | - self._new_id, self._new_executability) |
300 | + self._versioned, self._new_executability) |
301 | for id_set in id_sets: |
302 | new_ids.update(id_set) |
303 | return sorted(FinalPaths(self).get_paths(new_ids)) |
304 | |
305 | - def tree_file_id(self, trans_id): |
306 | - """Determine the file id associated with the trans_id in the tree""" |
307 | - path = self.tree_path(trans_id) |
308 | - if path is None: |
309 | - return None |
310 | - # the file is old; the old id is still valid |
311 | - if self._new_root == trans_id: |
312 | - return self._tree.path2id('') |
313 | - return self._tree.path2id(path) |
314 | - |
315 | def final_is_versioned(self, trans_id): |
316 | - return self.final_file_id(trans_id) is not None |
317 | - |
318 | - def final_file_id(self, trans_id): |
319 | - """Determine the file id after any changes are applied, or None. |
320 | - |
321 | - None indicates that the file will not be versioned after changes are |
322 | - applied. |
323 | - """ |
324 | - try: |
325 | - return self._new_id[trans_id] |
326 | - except KeyError: |
327 | - if trans_id in self._removed_id: |
328 | - return None |
329 | - return self.tree_file_id(trans_id) |
330 | - |
331 | - def inactive_file_id(self, trans_id): |
332 | - """Return the inactive file_id associated with a transaction id. |
333 | - That is, the one in the tree or in non_present_ids. |
334 | - The file_id may actually be active, too. |
335 | - """ |
336 | - file_id = self.tree_file_id(trans_id) |
337 | - if file_id is not None: |
338 | - return file_id |
339 | - for key, value in viewitems(self._non_present_ids): |
340 | - if value == trans_id: |
341 | - return key |
342 | + if trans_id in self._versioned: |
343 | + return True |
344 | + if trans_id in self._removed_id: |
345 | + return False |
346 | + orig_path = self.tree_path(trans_id) |
347 | + if orig_path is None: |
348 | + return False |
349 | + return self._tree.is_versioned(orig_path) |
350 | |
351 | def find_raw_conflicts(self): |
352 | """Find any violations of inventory or filesystem invariants""" |
353 | @@ -316,8 +244,11 @@ |
354 | for trans_id in self._removed_id: |
355 | path = self.tree_path(trans_id) |
356 | if path is not None: |
357 | - if self._tree.stored_kind(path) == 'directory': |
358 | - parents.append(trans_id) |
359 | + try: |
360 | + if self._tree.stored_kind(path) == 'directory': |
361 | + parents.append(trans_id) |
362 | + except errors.NoSuchFile: |
363 | + pass |
364 | elif self.tree_kind(trans_id) == 'directory': |
365 | parents.append(trans_id) |
366 | |
367 | @@ -392,7 +323,7 @@ |
368 | However, existing entries with no contents are okay. |
369 | """ |
370 | conflicts = [] |
371 | - for trans_id in self._new_id: |
372 | + for trans_id in self._versioned: |
373 | kind = self.final_kind(trans_id) |
374 | if kind == 'symlink' and not self._tree.supports_symlinks(): |
375 | # Ignore symlinks as they are not supported on this platform |
376 | @@ -598,7 +529,7 @@ |
377 | def _affected_ids(self): |
378 | """Return the set of transform ids affected by the transform""" |
379 | trans_ids = set(self._removed_id) |
380 | - trans_ids.update(self._new_id) |
381 | + trans_ids.update(self._versioned) |
382 | trans_ids.update(self._removed_contents) |
383 | trans_ids.update(self._new_contents) |
384 | trans_ids.update(self._new_executability) |
385 | @@ -606,130 +537,78 @@ |
386 | trans_ids.update(self._new_parent) |
387 | return trans_ids |
388 | |
389 | - def _get_file_id_maps(self): |
390 | - """Return mapping of file_ids to trans_ids in the to and from states""" |
391 | - trans_ids = self._affected_ids() |
392 | - from_trans_ids = {} |
393 | - to_trans_ids = {} |
394 | - # Build up two dicts: trans_ids associated with file ids in the |
395 | - # FROM state, vs the TO state. |
396 | - for trans_id in trans_ids: |
397 | - from_file_id = self.tree_file_id(trans_id) |
398 | - if from_file_id is not None: |
399 | - from_trans_ids[from_file_id] = trans_id |
400 | - to_file_id = self.final_file_id(trans_id) |
401 | - if to_file_id is not None: |
402 | - to_trans_ids[to_file_id] = trans_id |
403 | - return from_trans_ids, to_trans_ids |
404 | - |
405 | - def _from_file_data(self, from_trans_id, from_versioned, from_path): |
406 | - """Get data about a file in the from (tree) state |
407 | - |
408 | - Return a (name, parent, kind, executable) tuple |
409 | - """ |
410 | - from_path = self._tree_id_paths.get(from_trans_id) |
411 | - if from_versioned: |
412 | - # get data from working tree if versioned |
413 | - from_entry = next(self._tree.iter_entries_by_dir( |
414 | - specific_files=[from_path]))[1] |
415 | - from_name = from_entry.name |
416 | - from_parent = from_entry.parent_id |
417 | - else: |
418 | - from_entry = None |
419 | - if from_path is None: |
420 | - # File does not exist in FROM state |
421 | - from_name = None |
422 | - from_parent = None |
423 | - else: |
424 | - # File exists, but is not versioned. Have to use path- |
425 | - # splitting stuff |
426 | - from_name = os.path.basename(from_path) |
427 | - tree_parent = self.get_tree_parent(from_trans_id) |
428 | - from_parent = self.tree_file_id(tree_parent) |
429 | - if from_path is not None: |
430 | - from_kind, from_executable, from_stats = \ |
431 | - self._tree._comparison_data(from_entry, from_path) |
432 | - else: |
433 | - from_kind = None |
434 | - from_executable = False |
435 | - return from_name, from_parent, from_kind, from_executable |
436 | - |
437 | - def _to_file_data(self, to_trans_id, from_trans_id, from_executable): |
438 | - """Get data about a file in the to (target) state |
439 | - |
440 | - Return a (name, parent, kind, executable) tuple |
441 | - """ |
442 | - to_name = self.final_name(to_trans_id) |
443 | - to_kind = self.final_kind(to_trans_id) |
444 | - to_parent = self.final_file_id(self.final_parent(to_trans_id)) |
445 | - if to_trans_id in self._new_executability: |
446 | - to_executable = self._new_executability[to_trans_id] |
447 | - elif to_trans_id == from_trans_id: |
448 | - to_executable = from_executable |
449 | - else: |
450 | - to_executable = False |
451 | - return to_name, to_parent, to_kind, to_executable |
452 | - |
453 | - def iter_changes(self): |
454 | + def iter_changes(self, want_unversioned=False): |
455 | """Produce output in the same format as Tree.iter_changes. |
456 | |
457 | Will produce nonsensical results if invoked while inventory/filesystem |
458 | conflicts (as reported by TreeTransform.find_raw_conflicts()) are present. |
459 | - |
460 | - This reads the Transform, but only reproduces changes involving a |
461 | - file_id. Files that are not versioned in either of the FROM or TO |
462 | - states are not reflected. |
463 | """ |
464 | final_paths = FinalPaths(self) |
465 | - from_trans_ids, to_trans_ids = self._get_file_id_maps() |
466 | + trans_ids = self._affected_ids() |
467 | results = [] |
468 | - # Now iterate through all active file_ids |
469 | - for file_id in set(from_trans_ids).union(to_trans_ids): |
470 | + # Now iterate through all active paths |
471 | + for trans_id in trans_ids: |
472 | + from_path = self.tree_path(trans_id) |
473 | modified = False |
474 | - from_trans_id = from_trans_ids.get(file_id) |
475 | # find file ids, and determine versioning state |
476 | - if from_trans_id is None: |
477 | + if from_path is None: |
478 | from_versioned = False |
479 | - from_trans_id = to_trans_ids[file_id] |
480 | else: |
481 | - from_versioned = True |
482 | - to_trans_id = to_trans_ids.get(file_id) |
483 | - if to_trans_id is None: |
484 | + from_versioned = self._tree.is_versioned(from_path) |
485 | + if not want_unversioned and not from_versioned: |
486 | + from_path = None |
487 | + to_path = final_paths.get_path(trans_id) |
488 | + if to_path is None: |
489 | to_versioned = False |
490 | - to_trans_id = from_trans_id |
491 | - else: |
492 | - to_versioned = True |
493 | - |
494 | - if not from_versioned: |
495 | - from_path = None |
496 | - else: |
497 | - from_path = self._tree_id_paths.get(from_trans_id) |
498 | - if not to_versioned: |
499 | + else: |
500 | + to_versioned = self.final_is_versioned(trans_id) |
501 | + if not want_unversioned and not to_versioned: |
502 | to_path = None |
503 | - else: |
504 | - to_path = final_paths.get_path(to_trans_id) |
505 | - |
506 | - from_name, from_parent, from_kind, from_executable = \ |
507 | - self._from_file_data(from_trans_id, from_versioned, from_path) |
508 | - |
509 | - to_name, to_parent, to_kind, to_executable = \ |
510 | - self._to_file_data(to_trans_id, from_trans_id, from_executable) |
511 | - |
512 | - if from_kind != to_kind: |
513 | + |
514 | + if from_versioned: |
515 | + # get data from working tree if versioned |
516 | + from_entry = next(self._tree.iter_entries_by_dir( |
517 | + specific_files=[from_path]))[1] |
518 | + from_name = from_entry.name |
519 | + else: |
520 | + from_entry = None |
521 | + if from_path is None: |
522 | + # File does not exist in FROM state |
523 | + from_name = None |
524 | + else: |
525 | + # File exists, but is not versioned. Have to use path- |
526 | + # splitting stuff |
527 | + from_name = os.path.basename(from_path) |
528 | + if from_path is not None: |
529 | + from_kind, from_executable, from_stats = \ |
530 | + self._tree._comparison_data(from_entry, from_path) |
531 | + else: |
532 | + from_kind = None |
533 | + from_executable = False |
534 | + |
535 | + to_name = self.final_name(trans_id) |
536 | + to_kind = self.final_kind(trans_id) |
537 | + if trans_id in self._new_executability: |
538 | + to_executable = self._new_executability[trans_id] |
539 | + else: |
540 | + to_executable = from_executable |
541 | + |
542 | + if from_versioned and from_kind != to_kind: |
543 | modified = True |
544 | elif to_kind in ('file', 'symlink') and ( |
545 | - to_trans_id != from_trans_id |
546 | - or to_trans_id in self._new_contents): |
547 | + trans_id in self._new_contents): |
548 | modified = True |
549 | if (not modified and from_versioned == to_versioned |
550 | - and from_parent == to_parent and from_name == to_name |
551 | + and from_path == to_path |
552 | + and from_name == to_name |
553 | and from_executable == to_executable): |
554 | continue |
555 | + if (from_path, to_path) == (None, None): |
556 | + continue |
557 | results.append( |
558 | - InventoryTreeChange( |
559 | - file_id, (from_path, to_path), modified, |
560 | + TreeChange( |
561 | + (from_path, to_path), modified, |
562 | (from_versioned, to_versioned), |
563 | - (from_parent, to_parent), |
564 | (from_name, to_name), |
565 | (from_kind, to_kind), |
566 | (from_executable, to_executable))) |
567 | @@ -744,7 +623,7 @@ |
568 | The tree is a snapshot, and altering the TreeTransform will invalidate |
569 | it. |
570 | """ |
571 | - raise NotImplementedError(self.get_preview_tree) |
572 | + return GitPreviewTree(self) |
573 | |
574 | def commit(self, branch, message, merge_parents=None, strict=False, |
575 | timestamp=None, timezone=None, committer=None, authors=None, |
576 | @@ -771,7 +650,7 @@ |
577 | """ |
578 | self._check_malformed() |
579 | if strict: |
580 | - unversioned = set(self._new_contents).difference(set(self._new_id)) |
581 | + unversioned = set(self._new_contents).difference(set(self._versioned)) |
582 | for trans_id in unversioned: |
583 | if not self.final_is_versioned(trans_id): |
584 | raise errors.StrictCommitFailed() |
585 | @@ -828,104 +707,6 @@ |
586 | return () |
587 | return (self._tree.get_file_lines(path),) |
588 | |
589 | - def serialize(self, serializer): |
590 | - """Serialize this TreeTransform. |
591 | - |
592 | - :param serializer: A Serialiser like pack.ContainerSerializer. |
593 | - """ |
594 | - from .. import bencode |
595 | - new_name = {k.encode('utf-8'): v.encode('utf-8') |
596 | - for k, v in viewitems(self._new_name)} |
597 | - new_parent = {k.encode('utf-8'): v.encode('utf-8') |
598 | - for k, v in viewitems(self._new_parent)} |
599 | - new_id = {k.encode('utf-8'): v |
600 | - for k, v in viewitems(self._new_id)} |
601 | - new_executability = {k.encode('utf-8'): int(v) |
602 | - for k, v in viewitems(self._new_executability)} |
603 | - tree_path_ids = {k.encode('utf-8'): v.encode('utf-8') |
604 | - for k, v in viewitems(self._tree_path_ids)} |
605 | - non_present_ids = {k: v.encode('utf-8') |
606 | - for k, v in viewitems(self._non_present_ids)} |
607 | - removed_contents = [trans_id.encode('utf-8') |
608 | - for trans_id in self._removed_contents] |
609 | - removed_id = [trans_id.encode('utf-8') |
610 | - for trans_id in self._removed_id] |
611 | - attribs = { |
612 | - b'_id_number': self._id_number, |
613 | - b'_new_name': new_name, |
614 | - b'_new_parent': new_parent, |
615 | - b'_new_executability': new_executability, |
616 | - b'_new_id': new_id, |
617 | - b'_tree_path_ids': tree_path_ids, |
618 | - b'_removed_id': removed_id, |
619 | - b'_removed_contents': removed_contents, |
620 | - b'_non_present_ids': non_present_ids, |
621 | - } |
622 | - yield serializer.bytes_record(bencode.bencode(attribs), |
623 | - ((b'attribs',),)) |
624 | - for trans_id, kind in sorted(viewitems(self._new_contents)): |
625 | - if kind == 'file': |
626 | - with open(self._limbo_name(trans_id), 'rb') as cur_file: |
627 | - lines = cur_file.readlines() |
628 | - parents = self._get_parents_lines(trans_id) |
629 | - mpdiff = multiparent.MultiParent.from_lines(lines, parents) |
630 | - content = b''.join(mpdiff.to_patch()) |
631 | - if kind == 'directory': |
632 | - content = b'' |
633 | - if kind == 'symlink': |
634 | - content = self._read_symlink_target(trans_id) |
635 | - if not isinstance(content, bytes): |
636 | - content = content.encode('utf-8') |
637 | - yield serializer.bytes_record( |
638 | - content, ((trans_id.encode('utf-8'), kind.encode('ascii')),)) |
639 | - |
640 | - def deserialize(self, records): |
641 | - """Deserialize a stored TreeTransform. |
642 | - |
643 | - :param records: An iterable of (names, content) tuples, as per |
644 | - pack.ContainerPushParser. |
645 | - """ |
646 | - from .. import bencode |
647 | - names, content = next(records) |
648 | - attribs = bencode.bdecode(content) |
649 | - self._id_number = attribs[b'_id_number'] |
650 | - self._new_name = {k.decode('utf-8'): v.decode('utf-8') |
651 | - for k, v in viewitems(attribs[b'_new_name'])} |
652 | - self._new_parent = {k.decode('utf-8'): v.decode('utf-8') |
653 | - for k, v in viewitems(attribs[b'_new_parent'])} |
654 | - self._new_executability = { |
655 | - k.decode('utf-8'): bool(v) |
656 | - for k, v in viewitems(attribs[b'_new_executability'])} |
657 | - self._new_id = {k.decode('utf-8'): v |
658 | - for k, v in viewitems(attribs[b'_new_id'])} |
659 | - self._r_new_id = {v: k for k, v in viewitems(self._new_id)} |
660 | - self._tree_path_ids = {} |
661 | - self._tree_id_paths = {} |
662 | - for bytepath, trans_id in viewitems(attribs[b'_tree_path_ids']): |
663 | - path = bytepath.decode('utf-8') |
664 | - trans_id = trans_id.decode('utf-8') |
665 | - self._tree_path_ids[path] = trans_id |
666 | - self._tree_id_paths[trans_id] = path |
667 | - self._removed_id = {trans_id.decode('utf-8') |
668 | - for trans_id in attribs[b'_removed_id']} |
669 | - self._removed_contents = set( |
670 | - trans_id.decode('utf-8') |
671 | - for trans_id in attribs[b'_removed_contents']) |
672 | - self._non_present_ids = { |
673 | - k: v.decode('utf-8') |
674 | - for k, v in viewitems(attribs[b'_non_present_ids'])} |
675 | - for ((trans_id, kind),), content in records: |
676 | - trans_id = trans_id.decode('utf-8') |
677 | - kind = kind.decode('ascii') |
678 | - if kind == 'file': |
679 | - mpdiff = multiparent.MultiParent.from_patch(content) |
680 | - lines = mpdiff.to_lines(self._get_parents_texts(trans_id)) |
681 | - self.create_file(lines, trans_id) |
682 | - if kind == 'directory': |
683 | - self.create_directory(trans_id) |
684 | - if kind == 'symlink': |
685 | - self.create_symlink(content.decode('utf-8'), trans_id) |
686 | - |
687 | def create_file(self, contents, trans_id, mode_id=None, sha1=None): |
688 | """Schedule creation of a new file. |
689 | |
690 | @@ -997,6 +778,14 @@ |
691 | elif c[0] == 'missing parent': |
692 | # TODO(jelmer): This should not make it to here |
693 | yield TextConflict(fp.get_path(c[2])) |
694 | + elif c[0] == 'non-directory parent': |
695 | + yield TextConflict(fp.get_path(c[2])) |
696 | + elif c[0] == 'deleting parent': |
697 | + # TODO(jelmer): This should not make it to here |
698 | + yield TextConflict(fp.get_path(c[2])) |
699 | + elif c[0] == 'parent loop': |
700 | + # TODO(jelmer): This should not make it to here |
701 | + yield TextConflict(fp.get_path(c[2])) |
702 | else: |
703 | raise AssertionError('unknown conflict %s' % c[0]) |
704 | |
705 | @@ -1210,6 +999,7 @@ |
706 | path = None |
707 | trace.warning( |
708 | 'Unable to create symlink "%s" on this filesystem.' % (path,)) |
709 | + self._symlink_target[trans_id] = target |
710 | # We add symlink to _new_contents even if they are unsupported |
711 | # and not created. These entries are subsequently used to avoid |
712 | # conflicts on platforms that don't support symlink |
713 | @@ -1234,6 +1024,88 @@ |
714 | handle_orphan = conf.get('transform.orphan_policy') |
715 | handle_orphan(self, trans_id, parent_id) |
716 | |
717 | + def final_entry(self, trans_id): |
718 | + is_versioned = self.final_is_versioned(trans_id) |
719 | + fp = FinalPaths(self) |
720 | + tree_path = fp.get_path(trans_id) |
721 | + if trans_id in self._new_contents: |
722 | + path = self._limbo_name(trans_id) |
723 | + st = os.lstat(path) |
724 | + kind = mode_kind(st.st_mode) |
725 | + name = self.final_name(trans_id) |
726 | + file_id = self._tree.mapping.generate_file_id(tree_path) |
727 | + parent_id = self._tree.mapping.generate_file_id(os.path.dirname(tree_path)) |
728 | + if kind == 'directory': |
729 | + return GitTreeDirectory( |
730 | + file_id, self.final_name(trans_id), parent_id=parent_id), is_versioned |
731 | + executable = mode_is_executable(st.st_mode) |
732 | + mode = object_mode(kind, executable) |
733 | + blob = blob_from_path_and_stat(encode_git_path(path), st) |
734 | + if kind == 'symlink': |
735 | + return GitTreeSymlink( |
736 | + file_id, name, parent_id, |
737 | + decode_git_path(blob.data)), is_versioned |
738 | + elif kind == 'file': |
739 | + return GitTreeFile( |
740 | + file_id, name, executable=executable, parent_id=parent_id, |
741 | + git_sha1=blob.id, text_size=len(blob.data)), is_versioned |
742 | + else: |
743 | + raise AssertionError(kind) |
744 | + elif trans_id in self._removed_contents: |
745 | + return None, None |
746 | + else: |
747 | + orig_path = self.tree_path(trans_id) |
748 | + if orig_path is None: |
749 | + return None, None |
750 | + file_id = self._tree.mapping.generate_file_id(tree_path) |
751 | + if tree_path == '': |
752 | + parent_id = None |
753 | + else: |
754 | + parent_id = self._tree.mapping.generate_file_id(os.path.dirname(tree_path)) |
755 | + try: |
756 | + ie = next(self._tree.iter_entries_by_dir( |
757 | + specific_files=[orig_path]))[1] |
758 | + ie.file_id = file_id |
759 | + ie.parent_id = parent_id |
760 | + return ie, is_versioned |
761 | + except StopIteration: |
762 | + try: |
763 | + if self.tree_kind(trans_id) == 'directory': |
764 | + return GitTreeDirectory( |
765 | + file_id, self.final_name(trans_id), parent_id=parent_id), is_versioned |
766 | + except OSError as e: |
767 | + if e.errno != errno.ENOTDIR: |
768 | + raise |
769 | + return None, None |
770 | + |
771 | + def final_git_entry(self, trans_id): |
772 | + if trans_id in self._new_contents: |
773 | + path = self._limbo_name(trans_id) |
774 | + st = os.lstat(path) |
775 | + kind = mode_kind(st.st_mode) |
776 | + if kind == 'directory': |
777 | + return None, None |
778 | + executable = mode_is_executable(st.st_mode) |
779 | + mode = object_mode(kind, executable) |
780 | + blob = blob_from_path_and_stat(encode_git_path(path), st) |
781 | + elif trans_id in self._removed_contents: |
782 | + return None, None |
783 | + else: |
784 | + orig_path = self.tree_path(trans_id) |
785 | + kind = self._tree.kind(orig_path) |
786 | + executable = self._tree.is_executable(orig_path) |
787 | + mode = object_mode(kind, executable) |
788 | + if kind == 'symlink': |
789 | + contents = self._tree.get_symlink_target(orig_path) |
790 | + elif kind == 'file': |
791 | + contents = self._tree.get_file_text(orig_path) |
792 | + elif kind == 'directory': |
793 | + return None, None |
794 | + else: |
795 | + raise AssertionError(kind) |
796 | + blob = Blob.from_string(contents) |
797 | + return blob, mode |
798 | + |
799 | |
800 | class GitTreeTransform(DiskTreeTransform): |
801 | """Represent a tree transformation. |
802 | @@ -1454,20 +1326,11 @@ |
803 | self._limbo_children_names[parent][filename] = trans_id |
804 | return limbo_name |
805 | |
806 | - def version_file(self, trans_id, file_id=None): |
807 | - """Schedule a file to become versioned.""" |
808 | - if file_id is None: |
809 | - raise ValueError() |
810 | - unique_add(self._new_id, trans_id, file_id) |
811 | - unique_add(self._r_new_id, file_id, trans_id) |
812 | - |
813 | def cancel_versioning(self, trans_id): |
814 | """Undo a previous versioning of a file""" |
815 | - file_id = self._new_id[trans_id] |
816 | - del self._new_id[trans_id] |
817 | - del self._r_new_id[file_id] |
818 | + self._versioned.remove(trans_id) |
819 | |
820 | - def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None): |
821 | + def apply(self, no_conflicts=False, _mover=None): |
822 | """Apply all changes to the inventory and filesystem. |
823 | |
824 | If filesystem or inventory conflicts are present, MalformedTransform |
825 | @@ -1477,8 +1340,6 @@ |
826 | |
827 | :param no_conflicts: if True, the caller guarantees there are no |
828 | conflicts, so no check is made. |
829 | - :param precomputed_delta: An inventory delta to use instead of |
830 | - calculating one. |
831 | :param _mover: Supply an alternate FileMover, for testing |
832 | """ |
833 | for hook in MutableTree.hooks['pre_transform']: |
834 | @@ -1487,14 +1348,9 @@ |
835 | self._check_malformed() |
836 | self.rename_count = 0 |
837 | with ui.ui_factory.nested_progress_bar() as child_pb: |
838 | - if precomputed_delta is None: |
839 | - child_pb.update(gettext('Apply phase'), 0, 2) |
840 | - changes = self._generate_transform_changes() |
841 | - offset = 1 |
842 | - else: |
843 | - changes = [ |
844 | - (op, np, ie) for (op, np, fid, ie) in precomputed_delta] |
845 | - offset = 0 |
846 | + child_pb.update(gettext('Apply phase'), 0, 2) |
847 | + index_changes = self._generate_index_changes() |
848 | + offset = 1 |
849 | if _mover is None: |
850 | mover = _FileMover() |
851 | else: |
852 | @@ -1509,9 +1365,7 @@ |
853 | raise |
854 | else: |
855 | mover.apply_deletions() |
856 | - if self.final_file_id(self.root) is None: |
857 | - changes = [e for e in changes if e[0] != ''] |
858 | - self._tree._apply_transform_delta(changes) |
859 | + self._tree._apply_index_changes(index_changes) |
860 | self._done = True |
861 | self.finalize() |
862 | return _TransformResults(modified_paths, self.rename_count) |
863 | @@ -1594,97 +1448,354 @@ |
864 | self._new_contents.clear() |
865 | return modified_paths |
866 | |
867 | - def _inventory_altered(self): |
868 | - """Determine which trans_ids need new Inventory entries. |
869 | - |
870 | - An new entry is needed when anything that would be reflected by an |
871 | - inventory entry changes, including file name, file_id, parent file_id, |
872 | - file kind, and the execute bit. |
873 | - |
874 | - Some care is taken to return entries with real changes, not cases |
875 | - where the value is deleted and then restored to its original value, |
876 | - but some actually unchanged values may be returned. |
877 | - |
878 | - :returns: A list of (path, trans_id) for all items requiring an |
879 | - inventory change. Ordered by path. |
880 | - """ |
881 | + def _generate_index_changes(self): |
882 | + """Generate an inventory delta for the current transform.""" |
883 | + removed_id = set(self._removed_id) |
884 | + removed_id.update(self._removed_contents) |
885 | + changes = {} |
886 | changed_ids = set() |
887 | - # Find entries whose file_ids are new (or changed). |
888 | - new_file_id = set(t for t in self._new_id |
889 | - if self._new_id[t] != self.tree_file_id(t)) |
890 | - for id_set in [self._new_name, self._new_parent, new_file_id, |
891 | + for id_set in [self._new_name, self._new_parent, |
892 | self._new_executability]: |
893 | changed_ids.update(id_set) |
894 | - # removing implies a kind change |
895 | - changed_kind = set(self._removed_contents) |
896 | + for id_set in [self._new_name, self._new_parent]: |
897 | + removed_id.update(id_set) |
898 | # so does adding |
899 | - changed_kind.intersection_update(self._new_contents) |
900 | + changed_kind = set(self._new_contents) |
901 | # Ignore entries that are already known to have changed. |
902 | changed_kind.difference_update(changed_ids) |
903 | # to keep only the truly changed ones |
904 | changed_kind = (t for t in changed_kind |
905 | if self.tree_kind(t) != self.final_kind(t)) |
906 | - # all kind changes will alter the inventory |
907 | changed_ids.update(changed_kind) |
908 | - # To find entries with changed parent_ids, find parents which existed, |
909 | - # but changed file_id. |
910 | - # Now add all their children to the set. |
911 | - for parent_trans_id in new_file_id: |
912 | - changed_ids.update(self.iter_tree_children(parent_trans_id)) |
913 | - return sorted(FinalPaths(self).get_paths(changed_ids)) |
914 | - |
915 | - def _generate_transform_changes(self): |
916 | - """Generate an inventory delta for the current transform.""" |
917 | - changes = [] |
918 | - new_paths = self._inventory_altered() |
919 | - total_entries = len(new_paths) + len(self._removed_id) |
920 | + for t in changed_kind: |
921 | + if self.final_kind(t) == 'directory': |
922 | + removed_id.add(t) |
923 | + changed_ids.remove(t) |
924 | + new_paths = sorted(FinalPaths(self).get_paths(changed_ids)) |
925 | + total_entries = len(new_paths) + len(removed_id) |
926 | with ui.ui_factory.nested_progress_bar() as child_pb: |
927 | - for num, trans_id in enumerate(self._removed_id): |
928 | + for num, trans_id in enumerate(removed_id): |
929 | if (num % 10) == 0: |
930 | child_pb.update(gettext('removing file'), |
931 | num, total_entries) |
932 | - if trans_id == self._new_root: |
933 | - file_id = self._tree.path2id('') |
934 | - else: |
935 | - file_id = self.tree_file_id(trans_id) |
936 | - # File-id isn't really being deleted, just moved |
937 | - if file_id in self._r_new_id: |
938 | + try: |
939 | + path = self._tree_id_paths[trans_id] |
940 | + except KeyError: |
941 | continue |
942 | - path = self._tree_id_paths[trans_id] |
943 | - changes.append((path, None, None)) |
944 | - new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in |
945 | - new_paths) |
946 | + changes[path] = (None, None, None, None) |
947 | for num, (path, trans_id) in enumerate(new_paths): |
948 | if (num % 10) == 0: |
949 | child_pb.update(gettext('adding file'), |
950 | - num + len(self._removed_id), total_entries) |
951 | - file_id = new_path_file_ids[trans_id] |
952 | - if file_id is None: |
953 | - continue |
954 | + num + len(removed_id), total_entries) |
955 | + |
956 | kind = self.final_kind(trans_id) |
957 | if kind is None: |
958 | - kind = self._tree.stored_kind(self._tree.id2path(file_id)) |
959 | - parent_trans_id = self.final_parent(trans_id) |
960 | - parent_file_id = new_path_file_ids.get(parent_trans_id) |
961 | - if parent_file_id is None: |
962 | - parent_file_id = self.final_file_id(parent_trans_id) |
963 | - if trans_id in self._new_reference_revision: |
964 | - new_entry = inventory.TreeReference( |
965 | - file_id, |
966 | - self._new_name[trans_id], |
967 | - self.final_file_id(self._new_parent[trans_id]), |
968 | - None, self._new_reference_revision[trans_id]) |
969 | - else: |
970 | - new_entry = inventory.make_entry(kind, |
971 | - self.final_name(trans_id), |
972 | - parent_file_id, file_id) |
973 | - try: |
974 | - old_path = self._tree.id2path(new_entry.file_id) |
975 | - except errors.NoSuchId: |
976 | - old_path = None |
977 | - new_executability = self._new_executability.get(trans_id) |
978 | - if new_executability is not None: |
979 | - new_entry.executable = new_executability |
980 | - changes.append( |
981 | - (old_path, path, new_entry)) |
982 | - return changes |
983 | + continue |
984 | + versioned = self.final_is_versioned(trans_id) |
985 | + if not versioned: |
986 | + continue |
987 | + executability = self._new_executability.get(trans_id) |
988 | + reference_revision = self._new_reference_revision.get(trans_id) |
989 | + symlink_target = self._symlink_target.get(trans_id) |
990 | + changes[path] = ( |
991 | + kind, executability, reference_revision, symlink_target) |
992 | + return [(p, k, e, rr, st) for (p, (k, e, rr, st)) in changes.items()] |
993 | + |
994 | + |
995 | +class GitTransformPreview(GitTreeTransform): |
996 | + """A TreeTransform for generating preview trees. |
997 | + |
998 | + Unlike TreeTransform, this version works when the input tree is a |
999 | + RevisionTree, rather than a WorkingTree. As a result, it tends to ignore |
1000 | + unversioned files in the input tree. |
1001 | + """ |
1002 | + |
1003 | + def __init__(self, tree, pb=None, case_sensitive=True): |
1004 | + tree.lock_read() |
1005 | + limbodir = osutils.mkdtemp(prefix='bzr-limbo-') |
1006 | + DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive) |
1007 | + |
1008 | + def canonical_path(self, path): |
1009 | + return path |
1010 | + |
1011 | + def tree_kind(self, trans_id): |
1012 | + path = self.tree_path(trans_id) |
1013 | + if path is None: |
1014 | + return None |
1015 | + kind = self._tree.path_content_summary(path)[0] |
1016 | + if kind == 'missing': |
1017 | + kind = None |
1018 | + return kind |
1019 | + |
1020 | + def _set_mode(self, trans_id, mode_id, typefunc): |
1021 | + """Set the mode of new file contents. |
1022 | + The mode_id is the existing file to get the mode from (often the same |
1023 | + as trans_id). The operation is only performed if there's a mode match |
1024 | + according to typefunc. |
1025 | + """ |
1026 | + # is it ok to ignore this? probably |
1027 | + pass |
1028 | + |
1029 | + def iter_tree_children(self, parent_id): |
1030 | + """Iterate through the entry's tree children, if any""" |
1031 | + try: |
1032 | + path = self._tree_id_paths[parent_id] |
1033 | + except KeyError: |
1034 | + return |
1035 | + try: |
1036 | + for child in self._tree.iter_child_entries(path): |
1037 | + childpath = joinpath(path, child.name) |
1038 | + yield self.trans_id_tree_path(childpath) |
1039 | + except errors.NoSuchFile: |
1040 | + return |
1041 | + |
1042 | + def new_orphan(self, trans_id, parent_id): |
1043 | + raise NotImplementedError(self.new_orphan) |
1044 | + |
1045 | + |
1046 | +class GitPreviewTree(PreviewTree, GitTree): |
1047 | + """Partial implementation of Tree to support show_diff_trees""" |
1048 | + |
1049 | + def __init__(self, transform): |
1050 | + PreviewTree.__init__(self, transform) |
1051 | + self.store = transform._tree.store |
1052 | + self.mapping = transform._tree.mapping |
1053 | + self._final_paths = FinalPaths(transform) |
1054 | + |
1055 | + def supports_setting_file_ids(self): |
1056 | + return False |
1057 | + |
1058 | + def _supports_executable(self): |
1059 | + return self._transform._limbo_supports_executable() |
1060 | + |
1061 | + def walkdirs(self, prefix=''): |
1062 | + pending = [self._transform.root] |
1063 | + while len(pending) > 0: |
1064 | + parent_id = pending.pop() |
1065 | + children = [] |
1066 | + subdirs = [] |
1067 | + prefix = prefix.rstrip('/') |
1068 | + parent_path = self._final_paths.get_path(parent_id) |
1069 | + for child_id in self._all_children(parent_id): |
1070 | + path_from_root = self._final_paths.get_path(child_id) |
1071 | + basename = self._transform.final_name(child_id) |
1072 | + kind = self._transform.final_kind(child_id) |
1073 | + if kind is not None: |
1074 | + versioned_kind = kind |
1075 | + else: |
1076 | + kind = 'unknown' |
1077 | + versioned_kind = self._transform._tree.stored_kind( |
1078 | + path_from_root) |
1079 | + if versioned_kind == 'directory': |
1080 | + subdirs.append(child_id) |
1081 | + children.append((path_from_root, basename, kind, None, |
1082 | + versioned_kind)) |
1083 | + children.sort() |
1084 | + if parent_path.startswith(prefix): |
1085 | + yield parent_path, children |
1086 | + pending.extend(sorted(subdirs, key=self._final_paths.get_path, |
1087 | + reverse=True)) |
1088 | + |
1089 | + def iter_changes(self, from_tree, include_unchanged=False, |
1090 | + specific_files=None, pb=None, extra_trees=None, |
1091 | + require_versioned=True, want_unversioned=False): |
1092 | + """See InterTree.iter_changes. |
1093 | + |
1094 | + This has a fast path that is only used when the from_tree matches |
1095 | + the transform tree, and no fancy options are supplied. |
1096 | + """ |
1097 | + return InterTree.get(from_tree, self).iter_changes( |
1098 | + include_unchanged=include_unchanged, |
1099 | + specific_files=specific_files, |
1100 | + pb=pb, |
1101 | + extra_trees=extra_trees, |
1102 | + require_versioned=require_versioned, |
1103 | + want_unversioned=want_unversioned) |
1104 | + |
1105 | + def get_file(self, path): |
1106 | + """See Tree.get_file""" |
1107 | + trans_id = self._path2trans_id(path) |
1108 | + if trans_id is None: |
1109 | + raise errors.NoSuchFile(path) |
1110 | + if trans_id in self._transform._new_contents: |
1111 | + name = self._transform._limbo_name(trans_id) |
1112 | + return open(name, 'rb') |
1113 | + if trans_id in self._transform._removed_contents: |
1114 | + raise errors.NoSuchFile(path) |
1115 | + orig_path = self._transform.tree_path(trans_id) |
1116 | + return self._transform._tree.get_file(orig_path) |
1117 | + |
1118 | + def get_symlink_target(self, path): |
1119 | + """See Tree.get_symlink_target""" |
1120 | + trans_id = self._path2trans_id(path) |
1121 | + if trans_id is None: |
1122 | + raise errors.NoSuchFile(path) |
1123 | + if trans_id not in self._transform._new_contents: |
1124 | + orig_path = self._transform.tree_path(trans_id) |
1125 | + return self._transform._tree.get_symlink_target(orig_path) |
1126 | + name = self._transform._limbo_name(trans_id) |
1127 | + return osutils.readlink(name) |
1128 | + |
1129 | + def annotate_iter(self, path, default_revision=_mod_revision.CURRENT_REVISION): |
1130 | + trans_id = self._path2trans_id(path) |
1131 | + if trans_id is None: |
1132 | + return None |
1133 | + orig_path = self._transform.tree_path(trans_id) |
1134 | + if orig_path is not None: |
1135 | + old_annotation = self._transform._tree.annotate_iter( |
1136 | + orig_path, default_revision=default_revision) |
1137 | + else: |
1138 | + old_annotation = [] |
1139 | + try: |
1140 | + lines = self.get_file_lines(path) |
1141 | + except errors.NoSuchFile: |
1142 | + return None |
1143 | + return annotate.reannotate([old_annotation], lines, default_revision) |
1144 | + |
1145 | + def get_file_text(self, path): |
1146 | + """Return the byte content of a file. |
1147 | + |
1148 | + :param path: The path of the file. |
1149 | + |
1150 | + :returns: A single byte string for the whole file. |
1151 | + """ |
1152 | + with self.get_file(path) as my_file: |
1153 | + return my_file.read() |
1154 | + |
1155 | + def get_file_lines(self, path): |
1156 | + """Return the content of a file, as lines. |
1157 | + |
1158 | + :param path: The path of the file. |
1159 | + """ |
1160 | + return osutils.split_lines(self.get_file_text(path)) |
1161 | + |
1162 | + def extras(self): |
1163 | + possible_extras = set(self._transform.trans_id_tree_path(p) for p |
1164 | + in self._transform._tree.extras()) |
1165 | + possible_extras.update(self._transform._new_contents) |
1166 | + possible_extras.update(self._transform._removed_id) |
1167 | + for trans_id in possible_extras: |
1168 | + if not self._transform.final_is_versioned(trans_id): |
1169 | + yield self._final_paths._determine_path(trans_id) |
1170 | + |
1171 | + def path_content_summary(self, path): |
1172 | + trans_id = self._path2trans_id(path) |
1173 | + tt = self._transform |
1174 | + tree_path = tt.tree_path(trans_id) |
1175 | + kind = tt._new_contents.get(trans_id) |
1176 | + if kind is None: |
1177 | + if tree_path is None or trans_id in tt._removed_contents: |
1178 | + return 'missing', None, None, None |
1179 | + summary = tt._tree.path_content_summary(tree_path) |
1180 | + kind, size, executable, link_or_sha1 = summary |
1181 | + else: |
1182 | + link_or_sha1 = None |
1183 | + limbo_name = tt._limbo_name(trans_id) |
1184 | + if trans_id in tt._new_reference_revision: |
1185 | + kind = 'tree-reference' |
1186 | + if kind == 'file': |
1187 | + statval = os.lstat(limbo_name) |
1188 | + size = statval.st_size |
1189 | + if not tt._limbo_supports_executable(): |
1190 | + executable = False |
1191 | + else: |
1192 | + executable = statval.st_mode & S_IEXEC |
1193 | + else: |
1194 | + size = None |
1195 | + executable = None |
1196 | + if kind == 'symlink': |
1197 | + link_or_sha1 = os.readlink(limbo_name) |
1198 | + if not isinstance(link_or_sha1, text_type): |
1199 | + link_or_sha1 = link_or_sha1.decode(osutils._fs_enc) |
1200 | + executable = tt._new_executability.get(trans_id, executable) |
1201 | + return kind, size, executable, link_or_sha1 |
1202 | + |
1203 | + def get_file_mtime(self, path): |
1204 | + """See Tree.get_file_mtime""" |
1205 | + trans_id = self._path2trans_id(path) |
1206 | + if trans_id is None: |
1207 | + raise errors.NoSuchFile(path) |
1208 | + if trans_id not in self._transform._new_contents: |
1209 | + return self._transform._tree.get_file_mtime( |
1210 | + self._transform.tree_path(trans_id)) |
1211 | + name = self._transform._limbo_name(trans_id) |
1212 | + statval = os.lstat(name) |
1213 | + return statval.st_mtime |
1214 | + |
1215 | + def is_versioned(self, path): |
1216 | + trans_id = self._path2trans_id(path) |
1217 | + if trans_id is None: |
1218 | + # It doesn't exist, so it's not versioned. |
1219 | + return False |
1220 | + if trans_id in self._transform._versioned: |
1221 | + return True |
1222 | + if trans_id in self._transform._removed_id: |
1223 | + return False |
1224 | + orig_path = self._transform.tree_path(trans_id) |
1225 | + return self._transform._tree.is_versioned(orig_path) |
1226 | + |
1227 | + def iter_entries_by_dir(self, specific_files=None, recurse_nested=False): |
1228 | + if recurse_nested: |
1229 | + raise NotImplementedError( |
1230 | + 'follow tree references not yet supported') |
1231 | + |
1232 | + # This may not be a maximally efficient implementation, but it is |
1233 | + # reasonably straightforward. An implementation that grafts the |
1234 | + # TreeTransform changes onto the tree's iter_entries_by_dir results |
1235 | + # might be more efficient, but requires tricky inferences about stack |
1236 | + # position. |
1237 | + for trans_id, path in self._list_files_by_dir(): |
1238 | + entry, is_versioned = self._transform.final_entry(trans_id) |
1239 | + if entry is None: |
1240 | + continue |
1241 | + if not is_versioned and entry.kind != 'directory': |
1242 | + continue |
1243 | + if specific_files is not None and path not in specific_files: |
1244 | + continue |
1245 | + if entry is not None: |
1246 | + yield path, entry |
1247 | + |
1248 | + def _list_files_by_dir(self): |
1249 | + todo = [ROOT_PARENT] |
1250 | + while len(todo) > 0: |
1251 | + parent = todo.pop() |
1252 | + children = list(self._all_children(parent)) |
1253 | + paths = dict(zip(children, self._final_paths.get_paths(children))) |
1254 | + children.sort(key=paths.get) |
1255 | + todo.extend(reversed(children)) |
1256 | + for trans_id in children: |
1257 | + yield trans_id, paths[trans_id][0] |
1258 | + |
1259 | + def revision_tree(self, revision_id): |
1260 | + return self._transform._tree.revision_tree(revision_id) |
1261 | + |
1262 | + def _stat_limbo_file(self, trans_id): |
1263 | + name = self._transform._limbo_name(trans_id) |
1264 | + return os.lstat(name) |
1265 | + |
1266 | + def git_snapshot(self, want_unversioned=False): |
1267 | + extra = set() |
1268 | + os = [] |
1269 | + for trans_id, path in self._list_files_by_dir(): |
1270 | + if not self._transform.final_is_versioned(trans_id): |
1271 | + if not want_unversioned: |
1272 | + continue |
1273 | + extra.add(path) |
1274 | + o, mode = self._transform.final_git_entry(trans_id) |
1275 | + if o is not None: |
1276 | + self.store.add_object(o) |
1277 | + os.append((encode_git_path(path), o.id, mode)) |
1278 | + if not os: |
1279 | + return None, extra |
1280 | + return commit_tree(self.store, os), extra |
1281 | + |
1282 | + def iter_child_entries(self, path): |
1283 | + trans_id = self._path2trans_id(path) |
1284 | + if trans_id is None: |
1285 | + raise errors.NoSuchFile(path) |
1286 | + for child_trans_id in self._all_children(trans_id): |
1287 | + entry, is_versioned = self._transform.final_entry(trans_id) |
1288 | + if not is_versioned: |
1289 | + continue |
1290 | + if entry is not None: |
1291 | + yield entry |
1292 | |
1293 | === modified file 'breezy/git/tree.py' |
1294 | --- breezy/git/tree.py 2020-08-14 20:01:27 +0000 |
1295 | +++ breezy/git/tree.py 2020-08-20 19:27:58 +0000 |
1296 | @@ -278,6 +278,27 @@ |
1297 | """ |
1298 | raise NotImplementedError(self.snapshot) |
1299 | |
1300 | + def preview_transform(self, pb=None): |
1301 | + from .transform import GitTransformPreview |
1302 | + return GitTransformPreview(self, pb=pb) |
1303 | + |
1304 | + def find_related_paths_across_trees(self, paths, trees=[], |
1305 | + require_versioned=True): |
1306 | + if paths is None: |
1307 | + return None |
1308 | + if require_versioned: |
1309 | + trees = [self] + (trees if trees is not None else []) |
1310 | + unversioned = set() |
1311 | + for p in paths: |
1312 | + for t in trees: |
1313 | + if t.is_versioned(p): |
1314 | + break |
1315 | + else: |
1316 | + unversioned.add(p) |
1317 | + if unversioned: |
1318 | + raise errors.PathsNotVersionedError(unversioned) |
1319 | + return filter(self.is_versioned, paths) |
1320 | + |
1321 | def _submodule_info(self): |
1322 | if self._submodules is None: |
1323 | try: |
1324 | @@ -661,23 +682,6 @@ |
1325 | else: |
1326 | return (kind, None, None, None) |
1327 | |
1328 | - def find_related_paths_across_trees(self, paths, trees=[], |
1329 | - require_versioned=True): |
1330 | - if paths is None: |
1331 | - return None |
1332 | - if require_versioned: |
1333 | - trees = [self] + (trees if trees is not None else []) |
1334 | - unversioned = set() |
1335 | - for p in paths: |
1336 | - for t in trees: |
1337 | - if t.is_versioned(p): |
1338 | - break |
1339 | - else: |
1340 | - unversioned.add(p) |
1341 | - if unversioned: |
1342 | - raise errors.PathsNotVersionedError(unversioned) |
1343 | - return filter(self.is_versioned, paths) |
1344 | - |
1345 | def _iter_tree_contents(self, include_trees=False): |
1346 | if self.tree is None: |
1347 | return iter([]) |
1348 | @@ -729,10 +733,6 @@ |
1349 | mode_kind(mode))) |
1350 | yield path_decoded, children |
1351 | |
1352 | - def preview_transform(self, pb=None): |
1353 | - from .transform import GitTransformPreview |
1354 | - return GitTransformPreview(self, pb=pb) |
1355 | - |
1356 | |
1357 | def tree_delta_from_git_changes(changes, mappings, |
1358 | specific_files=None, |
1359 | @@ -967,8 +967,13 @@ |
1360 | fileid = mapping.generate_file_id(newpath_decoded) |
1361 | else: |
1362 | fileid = None |
1363 | + if oldkind == 'directory' and newkind == 'directory': |
1364 | + modified = False |
1365 | + else: |
1366 | + modified = (oldsha != newsha) or (oldmode != newmode) |
1367 | yield InventoryTreeChange( |
1368 | - fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha), |
1369 | + fileid, (oldpath_decoded, newpath_decoded), |
1370 | + modified, |
1371 | (oldversioned, newversioned), |
1372 | (oldparent, newparent), (oldname, newname), |
1373 | (oldkind, newkind), (oldexe, newexe), |
1374 | @@ -1222,11 +1227,32 @@ |
1375 | # TODO(jelmer): Keep track of dirty per index |
1376 | self._index_dirty = True |
1377 | |
1378 | - def _index_add_entry(self, path, kind, flags=0, reference_revision=None): |
1379 | + def _apply_index_changes(self, changes): |
1380 | + for (path, kind, executability, reference_revision, |
1381 | + symlink_target) in changes: |
1382 | + if kind is None or kind == 'directory': |
1383 | + (index, subpath) = self._lookup_index( |
1384 | + encode_git_path(path)) |
1385 | + try: |
1386 | + self._index_del_entry(index, subpath) |
1387 | + except KeyError: |
1388 | + pass |
1389 | + else: |
1390 | + self._versioned_dirs = None |
1391 | + else: |
1392 | + self._index_add_entry( |
1393 | + path, kind, |
1394 | + reference_revision=reference_revision, |
1395 | + symlink_target=symlink_target) |
1396 | + self.flush() |
1397 | + |
1398 | + def _index_add_entry( |
1399 | + self, path, kind, flags=0, reference_revision=None, |
1400 | + symlink_target=None): |
1401 | if kind == "directory": |
1402 | # Git indexes don't contain directories |
1403 | return |
1404 | - if kind == "file": |
1405 | + elif kind == "file": |
1406 | blob = Blob() |
1407 | try: |
1408 | file, stat_val = self.get_file_with_stat(path) |
1409 | @@ -1251,7 +1277,9 @@ |
1410 | # old index |
1411 | stat_val = os.stat_result( |
1412 | (stat.S_IFLNK, 0, 0, 0, 0, 0, 0, 0, 0, 0)) |
1413 | - blob.set_raw_string(encode_git_path(self.get_symlink_target(path))) |
1414 | + if symlink_target is None: |
1415 | + symlink_target = self.get_symlink_target(path) |
1416 | + blob.set_raw_string(encode_git_path(symlink_target)) |
1417 | # Add object to the repository if it didn't exist yet |
1418 | if blob.id not in self.store: |
1419 | self.store.add_object(blob) |
1420 | @@ -1543,25 +1571,6 @@ |
1421 | self._versioned_dirs = None |
1422 | self.flush() |
1423 | |
1424 | - def find_related_paths_across_trees(self, paths, trees=[], |
1425 | - require_versioned=True): |
1426 | - if paths is None: |
1427 | - return None |
1428 | - |
1429 | - if require_versioned: |
1430 | - trees = [self] + (trees if trees is not None else []) |
1431 | - unversioned = set() |
1432 | - for p in paths: |
1433 | - for t in trees: |
1434 | - if t.is_versioned(p): |
1435 | - break |
1436 | - else: |
1437 | - unversioned.add(p) |
1438 | - if unversioned: |
1439 | - raise errors.PathsNotVersionedError(unversioned) |
1440 | - |
1441 | - return filter(self.is_versioned, paths) |
1442 | - |
1443 | def path_content_summary(self, path): |
1444 | """See Tree.path_content_summary.""" |
1445 | try: |
1446 | @@ -1588,17 +1597,21 @@ |
1447 | return (kind, None, None, None) |
1448 | |
1449 | def stored_kind(self, relpath): |
1450 | + if relpath == '': |
1451 | + return 'directory' |
1452 | (index, index_path) = self._lookup_index(encode_git_path(relpath)) |
1453 | if index is None: |
1454 | - return kind |
1455 | + return None |
1456 | try: |
1457 | mode = index[index_path].mode |
1458 | except KeyError: |
1459 | - return kind |
1460 | + for p in index: |
1461 | + if osutils.is_inside( |
1462 | + decode_git_path(index_path), decode_git_path(p)): |
1463 | + return 'directory' |
1464 | + return None |
1465 | else: |
1466 | - if S_ISGITLINK(mode): |
1467 | - return 'tree-reference' |
1468 | - return 'directory' |
1469 | + return mode_kind(mode) |
1470 | |
1471 | def kind(self, relpath): |
1472 | kind = osutils.file_kind(self.abspath(relpath)) |
1473 | @@ -1616,10 +1629,6 @@ |
1474 | from .transform import GitTreeTransform |
1475 | return GitTreeTransform(self, pb=pb) |
1476 | |
1477 | - def preview_transform(self, pb=None): |
1478 | - from .transform import GitTransformPreview |
1479 | - return GitTransformPreview(self, pb=pb) |
1480 | - |
1481 | def has_changes(self, _from_tree=None): |
1482 | """Quickly check that the tree contains at least one commitable change. |
1483 | |
1484 | |
1485 | === modified file 'breezy/git/workingtree.py' |
1486 | --- breezy/git/workingtree.py 2020-08-10 03:38:37 +0000 |
1487 | +++ breezy/git/workingtree.py 2020-08-20 19:27:58 +0000 |
1488 | @@ -176,6 +176,12 @@ |
1489 | def describe(self): |
1490 | return 'Text conflict in %(path)s' % self.__dict__ |
1491 | |
1492 | + def __str__(self): |
1493 | + return self.describe() |
1494 | + |
1495 | + def __repr__(self): |
1496 | + return "%s(%r)" % (type(self).__name__, self.path) |
1497 | + |
1498 | |
1499 | class GitWorkingTree(MutableGitIndexTree, workingtree.WorkingTree): |
1500 | """A Git working tree.""" |
1501 | @@ -975,7 +981,6 @@ |
1502 | self._set_conflicted(path, path in by_path) |
1503 | |
1504 | def _set_conflicted(self, path, conflicted): |
1505 | - trace.mutter('change conflict: %r -> %r', path, conflicted) |
1506 | value = self.index[path] |
1507 | self._index_dirty = True |
1508 | if conflicted: |
1509 | @@ -1159,26 +1164,6 @@ |
1510 | def store_uncommitted(self): |
1511 | raise errors.StoringUncommittedNotSupported(self) |
1512 | |
1513 | - def _apply_transform_delta(self, changes): |
1514 | - for (old_path, new_path, ie) in changes: |
1515 | - if old_path is not None: |
1516 | - (index, old_subpath) = self._lookup_index( |
1517 | - encode_git_path(old_path)) |
1518 | - try: |
1519 | - self._index_del_entry(index, old_subpath) |
1520 | - except KeyError: |
1521 | - pass |
1522 | - else: |
1523 | - self._versioned_dirs = None |
1524 | - if new_path is not None and ie.kind != 'directory': |
1525 | - if ie.kind == 'tree-reference': |
1526 | - self._index_add_entry( |
1527 | - new_path, ie.kind, |
1528 | - reference_revision=ie.reference_revision) |
1529 | - else: |
1530 | - self._index_add_entry(new_path, ie.kind) |
1531 | - self.flush() |
1532 | - |
1533 | def annotate_iter(self, path, |
1534 | default_revision=_mod_revision.CURRENT_REVISION): |
1535 | """See Tree.annotate_iter |
1536 | @@ -1337,7 +1322,11 @@ |
1537 | def get_reference_revision(self, path, branch=None): |
1538 | hexsha = self._read_submodule_head(path) |
1539 | if hexsha is None: |
1540 | - return _mod_revision.NULL_REVISION |
1541 | + (index, subpath) = self._lookup_index( |
1542 | + encode_git_path(path)) |
1543 | + if subpath is None: |
1544 | + raise errors.NoSuchFile(path) |
1545 | + hexsha = index[subpath].sha |
1546 | return self.branch.lookup_foreign_revision_id(hexsha) |
1547 | |
1548 | def get_nested_tree(self, path): |
1549 | |
1550 | === modified file 'breezy/tests/per_tree/test_transform.py' |
1551 | --- breezy/tests/per_tree/test_transform.py 2020-08-06 22:38:03 +0000 |
1552 | +++ breezy/tests/per_tree/test_transform.py 2020-08-20 19:27:58 +0000 |
1553 | @@ -30,8 +30,10 @@ |
1554 | ) |
1555 | from ...tree import ( |
1556 | find_previous_path, |
1557 | + TreeChange, |
1558 | ) |
1559 | |
1560 | +from breezy.bzr.inventorytree import InventoryTreeChange |
1561 | |
1562 | from breezy.tests.per_tree import TestCaseWithTree |
1563 | |
1564 | @@ -46,11 +48,6 @@ |
1565 | |
1566 | class TestTransformPreview(TestCaseWithTree): |
1567 | |
1568 | - def setUp(self): |
1569 | - super(TestTransformPreview, self).setUp() |
1570 | - if not self.workingtree_format.supports_setting_file_ids: |
1571 | - self.skipTest('test not compatible with non-file-id trees yet') |
1572 | - |
1573 | def create_tree(self): |
1574 | tree = self.make_branch_and_tree('.') |
1575 | self.build_tree_contents([('a', b'content 1')]) |
1576 | @@ -153,20 +150,39 @@ |
1577 | (False, False), False)], |
1578 | list(preview_tree.iter_changes(revision_tree))) |
1579 | |
1580 | + def assertTreeChanges(self, expected, actual, tree): |
1581 | + # TODO(jelmer): Turn this into a matcher? |
1582 | + actual = list(actual) |
1583 | + if tree.supports_setting_file_ids(): |
1584 | + self.assertEqual(expected, actual) |
1585 | + else: |
1586 | + expected = [ |
1587 | + TreeChange(path=c.path, changed_content=c.changed_content, |
1588 | + versioned=c.versioned, name=c.name, |
1589 | + kind=c.kind, executable=c.executable, |
1590 | + copied=c.copied) for c in expected] |
1591 | + actual = [ |
1592 | + TreeChange(path=c.path, changed_content=c.changed_content, |
1593 | + versioned=c.versioned, name=c.name, |
1594 | + kind=c.kind, executable=c.executable, |
1595 | + copied=c.copied) for c in actual] |
1596 | + self.assertEqual(expected, actual) |
1597 | + |
1598 | def test_include_unchanged_succeeds(self): |
1599 | revision_tree, preview_tree = self.get_tree_and_preview_tree() |
1600 | changes = preview_tree.iter_changes(revision_tree, |
1601 | include_unchanged=True) |
1602 | |
1603 | root_id = revision_tree.path2id('') |
1604 | - root_entry = (root_id, ('', ''), False, (True, True), (None, None), |
1605 | - ('', ''), ('directory', 'directory'), (False, False), False) |
1606 | - a_entry = (revision_tree.path2id('a'), ('a', 'a'), True, (True, True), |
1607 | - (root_id, root_id), ('a', 'a'), ('file', 'file'), |
1608 | - (False, False), False) |
1609 | + root_entry = InventoryTreeChange( |
1610 | + root_id, ('', ''), False, (True, True), (None, None), |
1611 | + ('', ''), ('directory', 'directory'), (False, False), False) |
1612 | + a_entry = InventoryTreeChange( |
1613 | + revision_tree.path2id('a'), ('a', 'a'), True, (True, True), |
1614 | + (root_id, root_id), ('a', 'a'), ('file', 'file'), |
1615 | + (False, False), False) |
1616 | |
1617 | - |
1618 | - self.assertEqual([root_entry, a_entry], list(changes)) |
1619 | + self.assertTreeChanges([root_entry, a_entry], changes, preview_tree) |
1620 | |
1621 | def test_specific_files(self): |
1622 | revision_tree, preview_tree = self.get_tree_and_preview_tree() |
1623 | @@ -177,7 +193,6 @@ |
1624 | (root_id, root_id), ('a', 'a'), ('file', 'file'), |
1625 | (False, False), False) |
1626 | |
1627 | - |
1628 | self.assertEqual([a_entry], list(changes)) |
1629 | |
1630 | def test_want_unversioned(self): |
1631 | @@ -185,9 +200,10 @@ |
1632 | changes = preview_tree.iter_changes(revision_tree, |
1633 | want_unversioned=True) |
1634 | root_id = revision_tree.path2id('') |
1635 | - a_entry = (revision_tree.path2id('a'), ('a', 'a'), True, (True, True), |
1636 | - (root_id, root_id), ('a', 'a'), ('file', 'file'), |
1637 | - (False, False), False) |
1638 | + a_entry = InventoryTreeChange( |
1639 | + revision_tree.path2id('a'), ('a', 'a'), True, (True, True), |
1640 | + (root_id, root_id), ('a', 'a'), ('file', 'file'), |
1641 | + (False, False), False) |
1642 | |
1643 | self.assertEqual([a_entry], list(changes)) |
1644 | |
1645 | @@ -304,7 +320,9 @@ |
1646 | preview.new_file('new', preview.trans_id_tree_path('unchanged'), |
1647 | [b'contents'], b'new-id') |
1648 | preview_tree = preview.get_preview_tree() |
1649 | - self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new')) |
1650 | + self.assertTrue(preview_tree.is_versioned('unchanged/new')) |
1651 | + if self.workingtree_format.supports_setting_file_ids: |
1652 | + self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new')) |
1653 | |
1654 | def test_path2id_moved(self): |
1655 | tree = self.make_branch_and_tree('tree') |
1656 | @@ -339,7 +357,7 @@ |
1657 | self.assertEqual( |
1658 | 'new_name/child', |
1659 | find_previous_path(tree, preview_tree, 'old_name/child')) |
1660 | - if tree.supports_setting_file_ids: |
1661 | + if tree.supports_setting_file_ids(): |
1662 | self.assertEqual( |
1663 | tree.path2id('old_name/child'), |
1664 | preview_tree.path2id('new_name/child')) |
1665 | @@ -516,8 +534,11 @@ |
1666 | (revid1, b'a\n'), |
1667 | ] |
1668 | annotation = preview_tree.annotate_iter( |
1669 | + 'newname', default_revision=b'me:') |
1670 | + self.assertEqual(expected, annotation) |
1671 | + annotation = preview_tree.annotate_iter( |
1672 | 'file', default_revision=b'me:') |
1673 | - self.assertEqual(expected, annotation) |
1674 | + self.assertIs(None, annotation) |
1675 | |
1676 | def test_annotate_deleted(self): |
1677 | tree = self.make_branch_and_tree('tree') |
1678 | @@ -542,8 +563,8 @@ |
1679 | |
1680 | def test_is_executable(self): |
1681 | preview = self.get_empty_preview() |
1682 | - preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id') |
1683 | - preview.set_executability(True, preview.trans_id_file_id(b'file-id')) |
1684 | + trans_id = preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id') |
1685 | + preview.set_executability(True, trans_id) |
1686 | preview_tree = preview.get_preview_tree() |
1687 | self.assertEqual(True, preview_tree.is_executable('file')) |
1688 | |
1689 | @@ -568,6 +589,8 @@ |
1690 | preview.create_file([b'b\nc\nd\ne\n'], trans_id) |
1691 | self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')]) |
1692 | tree_a = preview.get_preview_tree() |
1693 | + if not getattr(tree_a, 'plan_file_merge', None): |
1694 | + self.skipTest('tree does not support file merge planning') |
1695 | tree_a.set_parent_ids([base_id]) |
1696 | self.addCleanup(tree_b.lock_read().unlock) |
1697 | self.assertEqual([ |
1698 | @@ -592,6 +615,8 @@ |
1699 | preview.create_file([b'b\nc\nd\ne\n'], trans_id) |
1700 | self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')]) |
1701 | tree_a = preview.get_preview_tree() |
1702 | + if not getattr(tree_a, 'plan_file_merge', None): |
1703 | + self.skipTest('tree does not support file merge planning') |
1704 | tree_a.set_parent_ids([base_id]) |
1705 | self.addCleanup(tree_b.lock_read().unlock) |
1706 | self.assertEqual([ |
1707 | @@ -624,6 +649,7 @@ |
1708 | preview.new_file('new-versioned-file', preview.root, [b'contents'], |
1709 | b'new-versioned-id') |
1710 | tree = preview.get_preview_tree() |
1711 | + self.assertEquals({'existing-file'}, set(work_tree.extras())) |
1712 | preview.unversion_file(preview.trans_id_tree_path('removed-file')) |
1713 | self.assertEqual({'new-file', 'removed-file', 'existing-file'}, |
1714 | set(tree.extras())) |
1715 | |
1716 | === modified file 'breezy/tests/per_workingtree/test_commit.py' |
1717 | --- breezy/tests/per_workingtree/test_commit.py 2020-08-10 03:38:37 +0000 |
1718 | +++ breezy/tests/per_workingtree/test_commit.py 2020-08-20 19:27:58 +0000 |
1719 | @@ -169,7 +169,7 @@ |
1720 | tree.lock_read() |
1721 | self.addCleanup(tree.unlock) |
1722 | changes = list(tree.iter_changes(tree.basis_tree())) |
1723 | - self.assertEqual(1, len(changes)) |
1724 | + self.assertEqual(1, len(changes), changes) |
1725 | self.assertEqual((None, 'a/b'), changes[0].path) |
1726 | |
1727 | def test_commit_sets_last_revision(self): |
1728 | |
1729 | === modified file 'breezy/tests/per_workingtree/test_merge_from_branch.py' |
1730 | --- breezy/tests/per_workingtree/test_merge_from_branch.py 2020-08-10 03:38:37 +0000 |
1731 | +++ breezy/tests/per_workingtree/test_merge_from_branch.py 2020-08-20 19:27:58 +0000 |
1732 | @@ -101,7 +101,7 @@ |
1733 | tree_a.lock_read() |
1734 | self.addCleanup(tree_a.unlock) |
1735 | changes = list(tree_a.iter_changes(tree_a.basis_tree())) |
1736 | - self.assertEqual(1, len(changes)) |
1737 | + self.assertEqual(1, len(changes), changes) |
1738 | |
1739 | def test_merge_type(self): |
1740 | this = self.make_branch_and_tree('this') |
1741 | |
1742 | === modified file 'breezy/tests/per_workingtree/test_smart_add.py' |
1743 | --- breezy/tests/per_workingtree/test_smart_add.py 2018-11-21 03:20:30 +0000 |
1744 | +++ breezy/tests/per_workingtree/test_smart_add.py 2020-08-20 19:27:58 +0000 |
1745 | @@ -324,6 +324,9 @@ |
1746 | self.build_tree_contents([('t1/file', b'content in t1')]) |
1747 | t1.commit('Changing file in t1') |
1748 | t1.merge_from_branch(tb.branch) |
1749 | + fnames = ['file.%s' % s for s in ('BASE', 'THIS', 'OTHER')] |
1750 | + for fn in fnames: |
1751 | + self.assertPathExists(os.path.join(t1.basedir, fn)) |
1752 | return t1 |
1753 | |
1754 | def test_cant_add_generated_files_implicitly(self): |
1755 | |
1756 | === modified file 'breezy/tests/per_workingtree/test_transform.py' |
1757 | --- breezy/tests/per_workingtree/test_transform.py 2020-08-15 03:35:17 +0000 |
1758 | +++ breezy/tests/per_workingtree/test_transform.py 2020-08-20 19:27:58 +0000 |
1759 | @@ -28,6 +28,7 @@ |
1760 | transform, |
1761 | urlutils, |
1762 | ) |
1763 | +from ...tree import TreeChange |
1764 | from ...bzr.conflicts import ( |
1765 | DeletingParent, |
1766 | DuplicateEntry, |
1767 | @@ -72,6 +73,8 @@ |
1768 | TransformRenameFailed, |
1769 | ) |
1770 | |
1771 | +from breezy.bzr.inventorytree import InventoryTreeChange |
1772 | + |
1773 | from breezy.tests.per_workingtree import TestCaseWithWorkingTree |
1774 | |
1775 | |
1776 | @@ -80,8 +83,6 @@ |
1777 | |
1778 | def setUp(self): |
1779 | super(TestTreeTransform, self).setUp() |
1780 | - if not self.workingtree_format.supports_setting_file_ids: |
1781 | - self.skipTest('test not compatible with non-file-id trees yet') |
1782 | self.wt = self.make_branch_and_tree('wt') |
1783 | |
1784 | def transform(self): |
1785 | @@ -180,6 +181,9 @@ |
1786 | |
1787 | def test_apply_informs_tree_of_observed_sha1(self): |
1788 | trans, root, contents, sha1 = self.transform_for_sha1_test() |
1789 | + from ...bzr.workingtree import InventoryWorkingTree |
1790 | + if not isinstance(self.wt, InventoryWorkingTree): |
1791 | + self.skipTest('not a bzr working tree') |
1792 | trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id', |
1793 | sha1=sha1) |
1794 | calls = [] |
1795 | @@ -330,7 +334,7 @@ |
1796 | transform.new_directory('', ROOT_PARENT, None) |
1797 | transform.delete_contents(transform.root) |
1798 | transform.fixup_new_roots() |
1799 | - self.assertNotIn(transform.root, transform._new_id) |
1800 | + self.assertNotIn(transform.root, getattr(transform, '_new_id', [])) |
1801 | |
1802 | def test_remove_root_fixup(self): |
1803 | transform, root = self.transform() |
1804 | @@ -427,14 +431,16 @@ |
1805 | if not tree.supports_tree_reference(): |
1806 | raise tests.TestNotApplicable( |
1807 | 'Tree format does not support references') |
1808 | + nested_tree = self.make_branch_and_tree('nested') |
1809 | + nested_revid = nested_tree.commit('commit') |
1810 | |
1811 | trans_id = transform.new_directory('reference', root, b'subtree-id') |
1812 | - transform.set_tree_reference(b'subtree-revision', trans_id) |
1813 | + transform.set_tree_reference(nested_revid, trans_id) |
1814 | transform.apply() |
1815 | tree.lock_read() |
1816 | self.addCleanup(tree.unlock) |
1817 | self.assertEqual( |
1818 | - b'subtree-revision', |
1819 | + nested_revid, |
1820 | tree.get_reference_revision('reference')) |
1821 | |
1822 | def test_conflicts(self): |
1823 | @@ -451,17 +457,29 @@ |
1824 | [('non-directory parent', trans_id)]) |
1825 | tinman_id = transform.trans_id_tree_path('tinman') |
1826 | transform.adjust_path('name', tinman_id, trans_id2) |
1827 | - self.assertEqual(transform.find_raw_conflicts(), |
1828 | - [('unversioned parent', tinman_id), |
1829 | - ('missing parent', tinman_id)]) |
1830 | + if self.wt.has_versioned_directories(): |
1831 | + self.assertEqual(transform.find_raw_conflicts(), |
1832 | + [('unversioned parent', tinman_id), |
1833 | + ('missing parent', tinman_id)]) |
1834 | + else: |
1835 | + self.assertEqual(transform.find_raw_conflicts(), |
1836 | + [('missing parent', tinman_id)]) |
1837 | lion_id = transform.create_path('lion', root) |
1838 | - self.assertEqual(transform.find_raw_conflicts(), |
1839 | - [('unversioned parent', tinman_id), |
1840 | - ('missing parent', tinman_id)]) |
1841 | + if self.wt.has_versioned_directories(): |
1842 | + self.assertEqual(transform.find_raw_conflicts(), |
1843 | + [('unversioned parent', tinman_id), |
1844 | + ('missing parent', tinman_id)]) |
1845 | + else: |
1846 | + self.assertEqual(transform.find_raw_conflicts(), |
1847 | + [('missing parent', tinman_id)]) |
1848 | transform.adjust_path('name', lion_id, trans_id2) |
1849 | - self.assertEqual(transform.find_raw_conflicts(), |
1850 | - [('unversioned parent', lion_id), |
1851 | - ('missing parent', lion_id)]) |
1852 | + if self.wt.has_versioned_directories(): |
1853 | + self.assertEqual(transform.find_raw_conflicts(), |
1854 | + [('unversioned parent', lion_id), |
1855 | + ('missing parent', lion_id)]) |
1856 | + else: |
1857 | + self.assertEqual(transform.find_raw_conflicts(), |
1858 | + [('missing parent', lion_id)]) |
1859 | transform.version_file(lion_id, file_id=b"Courage") |
1860 | self.assertEqual(transform.find_raw_conflicts(), |
1861 | [('missing parent', lion_id), |
1862 | @@ -496,11 +514,14 @@ |
1863 | fp = FinalPaths(transform2) |
1864 | self.assertTrue('oz/tip' in transform2._tree_path_ids) |
1865 | self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip')) |
1866 | - self.assertEqual(len(result), 2) |
1867 | + if self.wt.supports_setting_file_ids(): |
1868 | + self.assertEqual(len(result), 2) |
1869 | + self.assertEqual((result[1][0], result[1][2]), |
1870 | + ('duplicate id', newtip)) |
1871 | + else: |
1872 | + self.assertEqual(len(result), 1) |
1873 | self.assertEqual((result[0][0], result[0][1]), |
1874 | ('duplicate', newtip)) |
1875 | - self.assertEqual((result[1][0], result[1][2]), |
1876 | - ('duplicate id', newtip)) |
1877 | transform2.finalize() |
1878 | transform3 = self.wt.transform() |
1879 | self.addCleanup(transform3.finalize) |
1880 | @@ -874,80 +895,97 @@ |
1881 | conflicts.delete_versioned(oz) |
1882 | emerald = conflicts.trans_id_tree_path('oz/emeraldcity') |
1883 | # set up MissingParent conflict |
1884 | - munchkincity = conflicts.trans_id_file_id(b'munchkincity-id') |
1885 | + if conflicts._tree.supports_setting_file_ids(): |
1886 | + munchkincity = conflicts.trans_id_file_id(b'munchkincity-id') |
1887 | + else: |
1888 | + munchkincity = conflicts.assign_id() |
1889 | conflicts.adjust_path('munchkincity', root, munchkincity) |
1890 | conflicts.new_directory('auntem', munchkincity, b'auntem-id') |
1891 | # set up parent loop |
1892 | conflicts.adjust_path('emeraldcity', emerald, emerald) |
1893 | - return conflicts, emerald, oz, old_dorothy, new_dorothy |
1894 | + return conflicts, emerald, oz, old_dorothy, new_dorothy, munchkincity |
1895 | |
1896 | def test_conflict_resolution(self): |
1897 | - conflicts, emerald, oz, old_dorothy, new_dorothy =\ |
1898 | + conflicts, emerald, oz, old_dorothy, new_dorothy, munchkincity =\ |
1899 | self.get_conflicted() |
1900 | resolve_conflicts(conflicts) |
1901 | self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved') |
1902 | - self.assertIs(conflicts.final_file_id(old_dorothy), None) |
1903 | + if self.wt.supports_setting_file_ids(): |
1904 | + self.assertIs(conflicts.final_file_id(old_dorothy), None) |
1905 | + self.assertEqual(conflicts.final_file_id(new_dorothy), b'dorothy-id') |
1906 | self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy') |
1907 | - self.assertEqual(conflicts.final_file_id(new_dorothy), b'dorothy-id') |
1908 | self.assertEqual(conflicts.final_parent(emerald), oz) |
1909 | conflicts.apply() |
1910 | |
1911 | def test_cook_conflicts(self): |
1912 | - tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted() |
1913 | + tt, emerald, oz, old_dorothy, new_dorothy, munchkincity = self.get_conflicted() |
1914 | raw_conflicts = resolve_conflicts(tt) |
1915 | - cooked_conflicts = tt.cook_conflicts(raw_conflicts) |
1916 | - duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', |
1917 | - 'dorothy', None, b'dorothy-id') |
1918 | - self.assertEqual(cooked_conflicts[0], duplicate) |
1919 | - duplicate_id = DuplicateID('Unversioned existing file', |
1920 | - 'dorothy.moved', 'dorothy', None, |
1921 | - b'dorothy-id') |
1922 | - self.assertEqual(cooked_conflicts[1], duplicate_id) |
1923 | - missing_parent = MissingParent('Created directory', 'munchkincity', |
1924 | - b'munchkincity-id') |
1925 | - deleted_parent = DeletingParent('Not deleting', 'oz', b'oz-id') |
1926 | - self.assertEqual(cooked_conflicts[2], missing_parent) |
1927 | - unversioned_parent = UnversionedParent('Versioned directory', |
1928 | - 'munchkincity', |
1929 | - b'munchkincity-id') |
1930 | - unversioned_parent2 = UnversionedParent('Versioned directory', 'oz', |
1931 | - b'oz-id') |
1932 | - self.assertEqual(cooked_conflicts[3], unversioned_parent) |
1933 | - parent_loop = ParentLoop( |
1934 | - 'Cancelled move', 'oz/emeraldcity', |
1935 | - 'oz/emeraldcity', b'emerald-id', b'emerald-id') |
1936 | - self.assertEqual(cooked_conflicts[4], deleted_parent) |
1937 | - self.assertEqual(cooked_conflicts[5], unversioned_parent2) |
1938 | - self.assertEqual(cooked_conflicts[6], parent_loop) |
1939 | - self.assertEqual(len(cooked_conflicts), 7) |
1940 | + cooked_conflicts = list(tt.cook_conflicts(raw_conflicts)) |
1941 | + if self.wt.supports_setting_file_ids(): |
1942 | + duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', |
1943 | + 'dorothy', None, b'dorothy-id') |
1944 | + self.assertEqual(cooked_conflicts[0], duplicate) |
1945 | + duplicate_id = DuplicateID('Unversioned existing file', |
1946 | + 'dorothy.moved', 'dorothy', None, |
1947 | + b'dorothy-id') |
1948 | + self.assertEqual(cooked_conflicts[1], duplicate_id) |
1949 | + missing_parent = MissingParent('Created directory', 'munchkincity', |
1950 | + b'munchkincity-id') |
1951 | + deleted_parent = DeletingParent('Not deleting', 'oz', b'oz-id') |
1952 | + self.assertEqual(cooked_conflicts[2], missing_parent) |
1953 | + unversioned_parent = UnversionedParent('Versioned directory', |
1954 | + 'munchkincity', |
1955 | + b'munchkincity-id') |
1956 | + unversioned_parent2 = UnversionedParent('Versioned directory', 'oz', |
1957 | + b'oz-id') |
1958 | + self.assertEqual(cooked_conflicts[3], unversioned_parent) |
1959 | + parent_loop = ParentLoop( |
1960 | + 'Cancelled move', 'oz/emeraldcity', |
1961 | + 'oz/emeraldcity', b'emerald-id', b'emerald-id') |
1962 | + self.assertEqual(cooked_conflicts[4], deleted_parent) |
1963 | + self.assertEqual(cooked_conflicts[5], unversioned_parent2) |
1964 | + self.assertEqual(cooked_conflicts[6], parent_loop) |
1965 | + self.assertEqual(len(cooked_conflicts), 7) |
1966 | + else: |
1967 | + self.assertEqual( |
1968 | + set([c.path for c in cooked_conflicts]), |
1969 | + set(['oz/emeraldcity', 'oz', 'munchkincity', 'dorothy.moved'])) |
1970 | tt.finalize() |
1971 | |
1972 | def test_string_conflicts(self): |
1973 | - tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted() |
1974 | + tt, emerald, oz, old_dorothy, new_dorothy, munchkincity = self.get_conflicted() |
1975 | raw_conflicts = resolve_conflicts(tt) |
1976 | - cooked_conflicts = tt.cook_conflicts(raw_conflicts) |
1977 | + cooked_conflicts = list(tt.cook_conflicts(raw_conflicts)) |
1978 | tt.finalize() |
1979 | conflicts_s = [text_type(c) for c in cooked_conflicts] |
1980 | self.assertEqual(len(cooked_conflicts), len(conflicts_s)) |
1981 | - self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. ' |
1982 | - 'Moved existing file to ' |
1983 | - 'dorothy.moved.') |
1984 | - self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. ' |
1985 | - 'Unversioned existing file ' |
1986 | - 'dorothy.moved.') |
1987 | - self.assertEqual(conflicts_s[2], 'Conflict adding files to' |
1988 | - ' munchkincity. Created directory.') |
1989 | - self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not' |
1990 | - ' versioned, but has versioned' |
1991 | - ' children. Versioned directory.') |
1992 | - self.assertEqualDiff( |
1993 | - conflicts_s[4], "Conflict: can't delete oz because it" |
1994 | - " is not empty. Not deleting.") |
1995 | - self.assertEqual(conflicts_s[5], 'Conflict because oz is not' |
1996 | - ' versioned, but has versioned' |
1997 | - ' children. Versioned directory.') |
1998 | - self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into' |
1999 | - ' oz/emeraldcity. Cancelled move.') |
2000 | + if self.wt.supports_setting_file_ids(): |
2001 | + self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. ' |
2002 | + 'Moved existing file to ' |
2003 | + 'dorothy.moved.') |
2004 | + self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. ' |
2005 | + 'Unversioned existing file ' |
2006 | + 'dorothy.moved.') |
2007 | + self.assertEqual(conflicts_s[2], 'Conflict adding files to' |
2008 | + ' munchkincity. Created directory.') |
2009 | + self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not' |
2010 | + ' versioned, but has versioned' |
2011 | + ' children. Versioned directory.') |
2012 | + self.assertEqualDiff( |
2013 | + conflicts_s[4], "Conflict: can't delete oz because it" |
2014 | + " is not empty. Not deleting.") |
2015 | + self.assertEqual(conflicts_s[5], 'Conflict because oz is not' |
2016 | + ' versioned, but has versioned' |
2017 | + ' children. Versioned directory.') |
2018 | + self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into' |
2019 | + ' oz/emeraldcity. Cancelled move.') |
2020 | + else: |
2021 | + self.assertEqual( |
2022 | + {'Text conflict in dorothy.moved', |
2023 | + 'Text conflict in munchkincity', |
2024 | + 'Text conflict in oz', |
2025 | + 'Text conflict in oz/emeraldcity'}, |
2026 | + set([c for c in conflicts_s])) |
2027 | |
2028 | def prepare_wrong_parent_kind(self): |
2029 | tt, root = self.transform() |
2030 | @@ -967,13 +1005,14 @@ |
2031 | raw_conflicts = resolve_conflicts(tt) |
2032 | self.assertEqual({('non-directory parent', 'Created directory', |
2033 | 'new-3')}, raw_conflicts) |
2034 | - cooked_conflicts = tt.cook_conflicts(raw_conflicts) |
2035 | - if self.wt.supports_setting_file_ids(): |
2036 | + cooked_conflicts = list(tt.cook_conflicts(raw_conflicts)) |
2037 | + from ...bzr.workingtree import InventoryWorkingTree |
2038 | + if isinstance(tt._tree, InventoryWorkingTree): |
2039 | self.assertEqual([NonDirectoryParent('Created directory', 'parent.new', |
2040 | b'parent-id')], cooked_conflicts) |
2041 | else: |
2042 | - self.assertEqual([NonDirectoryParent('Created directory', 'parent.new', |
2043 | - None)], cooked_conflicts) |
2044 | + self.assertEqual(1, len(cooked_conflicts)) |
2045 | + self.assertEqual('parent.new', cooked_conflicts[0].path) |
2046 | tt.apply() |
2047 | if self.wt.has_versioned_directories(): |
2048 | self.assertFalse(self.wt.is_versioned('parent')) |
2049 | @@ -1186,20 +1225,35 @@ |
2050 | transform.apply() |
2051 | transform, root = self.transform() |
2052 | try: |
2053 | - self.assertEqual([], list(transform.iter_changes())) |
2054 | + self.assertTreeChanges(transform, []) |
2055 | old = transform.trans_id_tree_path('old') |
2056 | transform.unversion_file(old) |
2057 | - self.assertEqual([(b'id-1', ('old', None), False, (True, False), |
2058 | - (root_id, root_id), |
2059 | - ('old', 'old'), ('file', 'file'), |
2060 | - (True, True), False)], |
2061 | - list(transform.iter_changes())) |
2062 | + self.assertTreeChanges( |
2063 | + transform, [ |
2064 | + InventoryTreeChange( |
2065 | + b'id-1', ('old', None), False, (True, False), |
2066 | + (root_id, root_id), ('old', 'old'), ('file', 'file'), |
2067 | + (True, True), False)]) |
2068 | transform.new_directory('new', root, b'id-1') |
2069 | - self.assertEqual([(b'id-1', ('old', 'new'), True, (True, True), |
2070 | - (root_id, root_id), ('old', 'new'), |
2071 | - ('file', 'directory'), |
2072 | - (True, False), False)], |
2073 | - list(transform.iter_changes())) |
2074 | + if transform._tree.supports_setting_file_ids(): |
2075 | + self.assertTreeChanges( |
2076 | + transform, |
2077 | + [InventoryTreeChange( |
2078 | + b'id-1', ('old', 'new'), True, (True, True), |
2079 | + (root_id, root_id), ('old', 'new'), |
2080 | + ('file', 'directory'), |
2081 | + (True, False), False)]) |
2082 | + else: |
2083 | + self.assertTreeChanges( |
2084 | + transform, |
2085 | + [TreeChange( |
2086 | + (None, 'new'), False, (False, True), |
2087 | + (None, 'new'), (None, 'directory'), |
2088 | + (False, False), False), |
2089 | + TreeChange( |
2090 | + ('old', None), False, (True, False), |
2091 | + ('old', 'old'), ('file', 'file'), |
2092 | + (True, True), False)]) |
2093 | finally: |
2094 | transform.finalize() |
2095 | |
2096 | @@ -1212,11 +1266,15 @@ |
2097 | try: |
2098 | old = transform.trans_id_tree_path('old') |
2099 | transform.version_file(old, file_id=b'id-1') |
2100 | - self.assertEqual([(b'id-1', (None, 'old'), False, (False, True), |
2101 | - (root_id, root_id), |
2102 | - ('old', 'old'), ('file', 'file'), |
2103 | - (False, False), False)], |
2104 | - list(transform.iter_changes())) |
2105 | + changes = list(transform.iter_changes()) |
2106 | + self.assertEqual(1, len(changes)) |
2107 | + self.assertEqual((None, 'old'), changes[0].path) |
2108 | + self.assertEqual(False, changes[0].changed_content) |
2109 | + self.assertEqual((False, True), changes[0].versioned) |
2110 | + self.assertEqual((False, False), changes[0].executable) |
2111 | + if self.wt.supports_setting_file_ids(): |
2112 | + self.assertEqual((root_id, root_id), changes[0].parent_id) |
2113 | + self.assertEqual(b'id-1', changes[0].file_id) |
2114 | finally: |
2115 | transform.finalize() |
2116 | |
2117 | @@ -1232,78 +1290,120 @@ |
2118 | old = transform.trans_id_tree_path('old') |
2119 | subdir = transform.trans_id_tree_path('subdir') |
2120 | new = transform.trans_id_tree_path('new') |
2121 | - self.assertEqual([], list(transform.iter_changes())) |
2122 | + self.assertTreeChanges(transform, []) |
2123 | |
2124 | # content deletion |
2125 | transform.delete_contents(old) |
2126 | - self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True), |
2127 | - (root_id, root_id), |
2128 | - ('old', 'old'), ('file', None), |
2129 | - (False, False), False)], |
2130 | - list(transform.iter_changes())) |
2131 | + self.assertTreeChanges( |
2132 | + transform, |
2133 | + [InventoryTreeChange( |
2134 | + b'id-1', ('old', 'old'), True, (True, True), |
2135 | + (root_id, root_id), |
2136 | + ('old', 'old'), ('file', None), |
2137 | + (False, False), False)]) |
2138 | |
2139 | # content change |
2140 | transform.create_file([b'blah'], old) |
2141 | - self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True), |
2142 | - (root_id, root_id), |
2143 | - ('old', 'old'), ('file', 'file'), |
2144 | - (False, False), False)], |
2145 | - list(transform.iter_changes())) |
2146 | + self.assertTreeChanges( |
2147 | + transform, |
2148 | + [InventoryTreeChange( |
2149 | + b'id-1', ('old', 'old'), True, (True, True), |
2150 | + (root_id, root_id), |
2151 | + ('old', 'old'), ('file', 'file'), |
2152 | + (False, False), False)]) |
2153 | transform.cancel_deletion(old) |
2154 | - self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True), |
2155 | - (root_id, root_id), |
2156 | - ('old', 'old'), ('file', 'file'), |
2157 | - (False, False), False)], |
2158 | - list(transform.iter_changes())) |
2159 | + self.assertTreeChanges( |
2160 | + transform, |
2161 | + [InventoryTreeChange( |
2162 | + b'id-1', ('old', 'old'), True, (True, True), |
2163 | + (root_id, root_id), |
2164 | + ('old', 'old'), ('file', 'file'), |
2165 | + (False, False), False)]) |
2166 | transform.cancel_creation(old) |
2167 | |
2168 | # move file_id to a different file |
2169 | - self.assertEqual([], list(transform.iter_changes())) |
2170 | + self.assertTreeChanges(transform, []) |
2171 | transform.unversion_file(old) |
2172 | transform.version_file(new, file_id=b'id-1') |
2173 | transform.adjust_path('old', root, new) |
2174 | - self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True), |
2175 | - (root_id, root_id), |
2176 | - ('old', 'old'), ('file', 'file'), |
2177 | - (False, False), False)], |
2178 | - list(transform.iter_changes())) |
2179 | + if transform._tree.supports_setting_file_ids(): |
2180 | + self.assertTreeChanges( |
2181 | + transform, |
2182 | + [InventoryTreeChange( |
2183 | + b'id-1', ('old', 'old'), True, (True, True), |
2184 | + (root_id, root_id), |
2185 | + ('old', 'old'), ('file', 'file'), |
2186 | + (False, False), False)]) |
2187 | + else: |
2188 | + self.assertTreeChanges( |
2189 | + transform, |
2190 | + [TreeChange( |
2191 | + (None, 'old'), False, (False, True), |
2192 | + (None, 'old'), (None, 'file'), (False, False), False), |
2193 | + TreeChange( |
2194 | + ('old', None), False, (True, False), ('old', 'old'), |
2195 | + ('file', 'file'), (False, False), False)]) |
2196 | + |
2197 | transform.cancel_versioning(new) |
2198 | transform._removed_id = set() |
2199 | |
2200 | # execute bit |
2201 | - self.assertEqual([], list(transform.iter_changes())) |
2202 | + self.assertTreeChanges(transform, []) |
2203 | transform.set_executability(True, old) |
2204 | - self.assertEqual([(b'id-1', ('old', 'old'), False, (True, True), |
2205 | - (root_id, root_id), |
2206 | - ('old', 'old'), ('file', 'file'), |
2207 | - (False, True), False)], |
2208 | - list(transform.iter_changes())) |
2209 | + self.assertTreeChanges( |
2210 | + transform, |
2211 | + [InventoryTreeChange( |
2212 | + b'id-1', ('old', 'old'), False, (True, True), |
2213 | + (root_id, root_id), |
2214 | + ('old', 'old'), ('file', 'file'), |
2215 | + (False, True), False)]) |
2216 | transform.set_executability(None, old) |
2217 | |
2218 | # filename |
2219 | - self.assertEqual([], list(transform.iter_changes())) |
2220 | + self.assertTreeChanges(transform, []) |
2221 | transform.adjust_path('new', root, old) |
2222 | transform._new_parent = {} |
2223 | - self.assertEqual([(b'id-1', ('old', 'new'), False, (True, True), |
2224 | - (root_id, root_id), |
2225 | - ('old', 'new'), ('file', 'file'), |
2226 | - (False, False), False)], |
2227 | - list(transform.iter_changes())) |
2228 | + self.assertTreeChanges( |
2229 | + transform, |
2230 | + [InventoryTreeChange( |
2231 | + b'id-1', ('old', 'new'), False, (True, True), |
2232 | + (root_id, root_id), |
2233 | + ('old', 'new'), ('file', 'file'), |
2234 | + (False, False), False)]) |
2235 | transform._new_name = {} |
2236 | |
2237 | # parent directory |
2238 | - self.assertEqual([], list(transform.iter_changes())) |
2239 | + self.assertTreeChanges(transform, []) |
2240 | transform.adjust_path('new', subdir, old) |
2241 | transform._new_name = {} |
2242 | - self.assertEqual([(b'id-1', ('old', 'subdir/old'), False, |
2243 | - (True, True), (root_id, b'subdir-id'), ('old', 'old'), |
2244 | - ('file', 'file'), (False, False), False)], |
2245 | - list(transform.iter_changes())) |
2246 | + self.assertTreeChanges( |
2247 | + transform, [ |
2248 | + InventoryTreeChange( |
2249 | + b'id-1', ('old', 'subdir/old'), False, |
2250 | + (True, True), (root_id, b'subdir-id'), ('old', 'old'), |
2251 | + ('file', 'file'), (False, False), False)]) |
2252 | transform._new_path = {} |
2253 | - |
2254 | finally: |
2255 | transform.finalize() |
2256 | |
2257 | + def assertTreeChanges(self, tt, expected): |
2258 | + # TODO(jelmer): Turn this into a matcher? |
2259 | + actual = list(tt.iter_changes()) |
2260 | + if tt._tree.supports_setting_file_ids(): |
2261 | + self.assertEqual(expected, actual) |
2262 | + else: |
2263 | + expected = [ |
2264 | + TreeChange(path=c.path, changed_content=c.changed_content, |
2265 | + versioned=c.versioned, name=c.name, |
2266 | + kind=c.kind, executable=c.executable, |
2267 | + copied=c.copied) for c in expected] |
2268 | + actual = [ |
2269 | + TreeChange(path=c.path, changed_content=c.changed_content, |
2270 | + versioned=c.versioned, name=c.name, |
2271 | + kind=c.kind, executable=c.executable, |
2272 | + copied=c.copied) for c in actual] |
2273 | + self.assertEqual(expected, actual) |
2274 | + |
2275 | def test_iter_changes_modified_bleed(self): |
2276 | root_id = self.wt.path2id('') |
2277 | """Modified flag should not bleed from one change to another""" |
2278 | @@ -1317,17 +1417,17 @@ |
2279 | transform.apply() |
2280 | transform, root = self.transform() |
2281 | try: |
2282 | - transform.delete_contents(transform.trans_id_file_id(b'id-1')) |
2283 | - transform.set_executability(True, |
2284 | - transform.trans_id_file_id(b'id-2')) |
2285 | - self.assertEqual( |
2286 | - [(b'id-1', (u'file1', u'file1'), True, (True, True), |
2287 | - (root_id, root_id), ('file1', u'file1'), |
2288 | - ('file', None), (False, False), False), |
2289 | - (b'id-2', (u'file2', u'file2'), False, (True, True), |
2290 | - (root_id, root_id), ('file2', u'file2'), |
2291 | - ('file', 'file'), (False, True), False)], |
2292 | - list(transform.iter_changes())) |
2293 | + transform.delete_contents(transform.trans_id_tree_path('file1')) |
2294 | + transform.set_executability(True, transform.trans_id_tree_path('file2')) |
2295 | + self.assertTreeChanges(transform, [ |
2296 | + InventoryTreeChange( |
2297 | + b'id-1', (u'file1', u'file1'), True, (True, True), |
2298 | + (root_id, root_id), ('file1', u'file1'), |
2299 | + ('file', None), (False, False), False), |
2300 | + InventoryTreeChange( |
2301 | + b'id-2', (u'file2', u'file2'), False, (True, True), |
2302 | + (root_id, root_id), ('file2', u'file2'), |
2303 | + ('file', 'file'), (False, True), False)]) |
2304 | finally: |
2305 | transform.finalize() |
2306 | |
2307 | @@ -1345,12 +1445,17 @@ |
2308 | floater = transform.trans_id_tree_path('floater') |
2309 | try: |
2310 | transform.adjust_path('flitter', root, floater) |
2311 | - self.assertEqual([(b'floater-id', ('floater', 'flitter'), False, |
2312 | - (True, True), |
2313 | - (root_id, root_id), |
2314 | - ('floater', 'flitter'), |
2315 | - (None, None), (False, False), False)], |
2316 | - list(transform.iter_changes())) |
2317 | + if self.wt.has_versioned_directories(): |
2318 | + self.assertTreeChanges( |
2319 | + transform, |
2320 | + [InventoryTreeChange( |
2321 | + b'floater-id', ('floater', 'flitter'), False, |
2322 | + (True, True), |
2323 | + (root_id, root_id), |
2324 | + ('floater', 'flitter'), |
2325 | + (None, None), (False, False), False)]) |
2326 | + else: |
2327 | + self.assertTreeChanges(transform, []) |
2328 | finally: |
2329 | transform.finalize() |
2330 | |
2331 | @@ -1364,14 +1469,14 @@ |
2332 | try: |
2333 | old = transform.trans_id_tree_path('old') |
2334 | subdir = transform.trans_id_tree_path('subdir') |
2335 | - self.assertEqual([], list(transform.iter_changes())) |
2336 | + self.assertTreeChanges(transform, []) |
2337 | transform.delete_contents(subdir) |
2338 | transform.create_directory(subdir) |
2339 | transform.set_executability(False, old) |
2340 | transform.unversion_file(old) |
2341 | transform.version_file(old, file_id=b'id-1') |
2342 | transform.adjust_path('old', root, old) |
2343 | - self.assertEqual([], list(transform.iter_changes())) |
2344 | + self.assertTreeChanges(transform, []) |
2345 | finally: |
2346 | transform.finalize() |
2347 | |
2348 | |
2349 | === modified file 'breezy/tests/per_workingtree/test_unversion.py' |
2350 | --- breezy/tests/per_workingtree/test_unversion.py 2020-08-10 03:38:37 +0000 |
2351 | +++ breezy/tests/per_workingtree/test_unversion.py 2020-08-20 19:27:58 +0000 |
2352 | @@ -179,7 +179,7 @@ |
2353 | if tree_b.has_versioned_directories(): |
2354 | self.assertEqual(4, num_conflicts) |
2355 | else: |
2356 | - self.assertEqual(3, num_conflicts) |
2357 | + self.assertEqual(1, num_conflicts) |
2358 | |
2359 | self.assertThat( |
2360 | tree_b, |
2361 | |
2362 | === modified file 'breezy/tests/per_workingtree/test_workingtree.py' |
2363 | --- breezy/tests/per_workingtree/test_workingtree.py 2020-08-10 00:21:26 +0000 |
2364 | +++ breezy/tests/per_workingtree/test_workingtree.py 2020-08-20 19:27:58 +0000 |
2365 | @@ -216,13 +216,11 @@ |
2366 | def test_lock_locks_branch(self): |
2367 | tree = self.make_branch_and_tree('.') |
2368 | self.assertEqual(None, tree.branch.peek_lock_mode()) |
2369 | - tree.lock_read() |
2370 | - self.assertEqual('r', tree.branch.peek_lock_mode()) |
2371 | - tree.unlock() |
2372 | + with tree.lock_read(): |
2373 | + self.assertEqual('r', tree.branch.peek_lock_mode()) |
2374 | self.assertEqual(None, tree.branch.peek_lock_mode()) |
2375 | - tree.lock_write() |
2376 | - self.assertEqual('w', tree.branch.peek_lock_mode()) |
2377 | - tree.unlock() |
2378 | + with tree.lock_write(): |
2379 | + self.assertEqual('w', tree.branch.peek_lock_mode()) |
2380 | self.assertEqual(None, tree.branch.peek_lock_mode()) |
2381 | |
2382 | def test_revert(self): |
2383 | @@ -1013,6 +1011,16 @@ |
2384 | if tree.branch.repository._format.supports_versioned_directories: |
2385 | self.assertEqual('directory', tree.stored_kind('b')) |
2386 | |
2387 | + def test_stored_kind_nonexistent(self): |
2388 | + tree = self.make_branch_and_tree('tree') |
2389 | + tree.lock_write() |
2390 | + self.assertRaises(errors.NoSuchFile, tree.stored_kind, 'a') |
2391 | + self.addCleanup(tree.unlock) |
2392 | + self.build_tree(['tree/a']) |
2393 | + self.assertRaises(errors.NoSuchFile, tree.stored_kind, 'a') |
2394 | + tree.add(['a']) |
2395 | + self.assertIs('file', tree.stored_kind('a')) |
2396 | + |
2397 | def test_missing_file_sha1(self): |
2398 | """If a file is missing, its sha1 should be reported as None.""" |
2399 | tree = self.make_branch_and_tree('.') |
2400 | |
2401 | === modified file 'breezy/transform.py' |
2402 | --- breezy/transform.py 2020-08-14 20:01:27 +0000 |
2403 | +++ breezy/transform.py 2020-08-20 19:27:58 +0000 |
2404 | @@ -355,6 +355,8 @@ |
2405 | contents insertion command) |
2406 | """ |
2407 | if trans_id in self._new_contents: |
2408 | + if trans_id in self._new_reference_revision: |
2409 | + return 'tree-reference' |
2410 | return self._new_contents[trans_id] |
2411 | elif trans_id in self._removed_contents: |
2412 | return None |
2413 | @@ -1238,7 +1240,11 @@ |
2414 | def resolve_non_directory_parent(tt, path_tree, c_type, parent_id): |
2415 | parent_parent = tt.final_parent(parent_id) |
2416 | parent_name = tt.final_name(parent_id) |
2417 | - parent_file_id = tt.final_file_id(parent_id) |
2418 | + # TODO(jelmer): Make this code transform-specific |
2419 | + if tt._tree.supports_setting_file_ids(): |
2420 | + parent_file_id = tt.final_file_id(parent_id) |
2421 | + else: |
2422 | + parent_file_id = b'DUMMY' |
2423 | new_parent_id = tt.new_directory(parent_name + '.new', |
2424 | parent_parent, parent_file_id) |
2425 | _reparent_transform_children(tt, parent_id, new_parent_id) |
2426 | @@ -1359,6 +1365,9 @@ |
2427 | self._all_children_cache = {} |
2428 | self._final_name_cache = {} |
2429 | |
2430 | + def supports_setting_file_ids(self): |
2431 | + raise NotImplementedError(self.supports_setting_file_ids) |
2432 | + |
2433 | @property |
2434 | def _by_parent(self): |
2435 | if self.__by_parent is None: |
2436 | |
2437 | === modified file 'breezy/workingtree.py' |
2438 | --- breezy/workingtree.py 2020-08-09 18:10:01 +0000 |
2439 | +++ breezy/workingtree.py 2020-08-20 19:27:58 +0000 |
2440 | @@ -711,8 +711,12 @@ |
2441 | |
2442 | def get_symlink_target(self, path): |
2443 | abspath = self.abspath(path) |
2444 | - target = osutils.readlink(abspath) |
2445 | - return target |
2446 | + try: |
2447 | + return osutils.readlink(abspath) |
2448 | + except OSError as e: |
2449 | + if getattr(e, 'errno', None) == errno.ENOENT: |
2450 | + raise errors.NoSuchFile(path) |
2451 | + raise |
2452 | |
2453 | def subsume(self, other_tree): |
2454 | raise NotImplementedError(self.subsume) |