Merge lp:~jelmer/brz/transform into lp:brz/3.1

Proposed by Jelmer Vernooij
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
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)

Subscribers

People subscribed via source and target branches