Merge lp:~jelmer/brz/split-conflict-pass 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/split-conflict-pass
Merge into: lp:brz/3.1
Diff against target: 234 lines (+120/-99)
1 file modified
breezy/transform.py (+120/-99)
To merge this branch: bzr merge lp:~jelmer/brz/split-conflict-pass
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve
Review via email: mp+388933@code.launchpad.net

Commit message

Factor out resolvers in conflict resolution handling.

Description of the change

Factor out resolvers in conflict resolution handling.

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/transform.py'
2--- breezy/transform.py 2020-08-07 00:10:46 +0000
3+++ breezy/transform.py 2020-08-07 20:23:16 +0000
4@@ -1146,6 +1146,121 @@
5 raise MalformedTransform(conflicts=conflicts)
6
7
8+def resolve_duplicate_id(tt, path_tree, c_type, old_trans_id, trans_id):
9+ tt.unversion_file(old_trans_id)
10+ yield (c_type, 'Unversioned existing file', old_trans_id, trans_id)
11+
12+
13+def resolve_duplicate(tt, path_tree, c_type, last_trans_id, trans_id, name):
14+ # files that were renamed take precedence
15+ final_parent = tt.final_parent(last_trans_id)
16+ if tt.path_changed(last_trans_id):
17+ existing_file, new_file = trans_id, last_trans_id
18+ else:
19+ existing_file, new_file = last_trans_id, trans_id
20+ new_name = tt.final_name(existing_file) + '.moved'
21+ tt.adjust_path(new_name, final_parent, existing_file)
22+ yield (c_type, 'Moved existing file to', existing_file, new_file)
23+
24+
25+def resolve_parent_loop(tt, path_tree, c_type, cur):
26+ # break the loop by undoing one of the ops that caused the loop
27+ while not tt.path_changed(cur):
28+ cur = tt.final_parent(cur)
29+ yield (c_type, 'Cancelled move', cur, tt.final_parent(cur),)
30+ tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
31+
32+
33+def resolve_missing_parent(tt, path_tree, c_type, trans_id):
34+ if trans_id in tt._removed_contents:
35+ cancel_deletion = True
36+ orphans = tt._get_potential_orphans(trans_id)
37+ if orphans:
38+ cancel_deletion = False
39+ # All children are orphans
40+ for o in orphans:
41+ try:
42+ tt.new_orphan(o, trans_id)
43+ except OrphaningError:
44+ # Something bad happened so we cancel the directory
45+ # deletion which will leave it in place with a
46+ # conflict. The user can deal with it from there.
47+ # Note that this also catch the case where we don't
48+ # want to create orphans and leave the directory in
49+ # place.
50+ cancel_deletion = True
51+ break
52+ if cancel_deletion:
53+ # Cancel the directory deletion
54+ tt.cancel_deletion(trans_id)
55+ yield ('deleting parent', 'Not deleting', trans_id)
56+ else:
57+ create = True
58+ try:
59+ tt.final_name(trans_id)
60+ except NoFinalPath:
61+ if path_tree is not None:
62+ file_id = tt.final_file_id(trans_id)
63+ if file_id is None:
64+ file_id = tt.inactive_file_id(trans_id)
65+ _, entry = next(path_tree.iter_entries_by_dir(
66+ specific_files=[path_tree.id2path(file_id)]))
67+ # special-case the other tree root (move its
68+ # children to current root)
69+ if entry.parent_id is None:
70+ create = False
71+ moved = _reparent_transform_children(
72+ tt, trans_id, tt.root)
73+ for child in moved:
74+ yield (c_type, 'Moved to root', child)
75+ else:
76+ parent_trans_id = tt.trans_id_file_id(
77+ entry.parent_id)
78+ tt.adjust_path(entry.name, parent_trans_id,
79+ trans_id)
80+ if create:
81+ tt.create_directory(trans_id)
82+ yield (c_type, 'Created directory', trans_id)
83+
84+
85+def resolve_unversioned_parent(tt, path_tree, c_type, trans_id):
86+ file_id = tt.inactive_file_id(trans_id)
87+ # special-case the other tree root (move its children instead)
88+ if path_tree and path_tree.path2id('') == file_id:
89+ # This is the root entry, skip it
90+ return
91+ tt.version_file(trans_id, file_id=file_id)
92+ yield (c_type, 'Versioned directory', trans_id)
93+
94+
95+def resolve_non_directory_parent(tt, path_tree, c_type, parent_id):
96+ parent_parent = tt.final_parent(parent_id)
97+ parent_name = tt.final_name(parent_id)
98+ parent_file_id = tt.final_file_id(parent_id)
99+ new_parent_id = tt.new_directory(parent_name + '.new',
100+ parent_parent, parent_file_id)
101+ _reparent_transform_children(tt, parent_id, new_parent_id)
102+ if parent_file_id is not None:
103+ tt.unversion_file(parent_id)
104+ yield (c_type, 'Created directory', new_parent_id)
105+
106+
107+def resolve_versioning_no_contents(tt, path_tree, c_type, trans_id):
108+ tt.cancel_versioning(trans_id)
109+ return []
110+
111+
112+CONFLICT_RESOLVERS = {
113+ 'duplicate id': resolve_duplicate_id,
114+ 'duplicate': resolve_duplicate,
115+ 'parent loop': resolve_parent_loop,
116+ 'missing parent': resolve_missing_parent,
117+ 'unversioned parent': resolve_unversioned_parent,
118+ 'non-directory parent': resolve_non_directory_parent,
119+ 'versioning no contents': resolve_versioning_no_contents,
120+}
121+
122+
123 def conflict_pass(tt, conflicts, path_tree=None):
124 """Resolve some classes of conflicts.
125
126@@ -1154,105 +1269,11 @@
127 :param path_tree: A Tree to get supplemental paths from
128 """
129 new_conflicts = set()
130- for c_type, conflict in ((c[0], c) for c in conflicts):
131- if c_type == 'duplicate id':
132- tt.unversion_file(conflict[1])
133- new_conflicts.add((c_type, 'Unversioned existing file',
134- conflict[1], conflict[2], ))
135- elif c_type == 'duplicate':
136- # files that were renamed take precedence
137- final_parent = tt.final_parent(conflict[1])
138- if tt.path_changed(conflict[1]):
139- existing_file, new_file = conflict[2], conflict[1]
140- else:
141- existing_file, new_file = conflict[1], conflict[2]
142- new_name = tt.final_name(existing_file) + '.moved'
143- tt.adjust_path(new_name, final_parent, existing_file)
144- new_conflicts.add((c_type, 'Moved existing file to',
145- existing_file, new_file))
146- elif c_type == 'parent loop':
147- # break the loop by undoing one of the ops that caused the loop
148- cur = conflict[1]
149- while not tt.path_changed(cur):
150- cur = tt.final_parent(cur)
151- new_conflicts.add((c_type, 'Cancelled move', cur,
152- tt.final_parent(cur),))
153- tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
154-
155- elif c_type == 'missing parent':
156- trans_id = conflict[1]
157- if trans_id in tt._removed_contents:
158- cancel_deletion = True
159- orphans = tt._get_potential_orphans(trans_id)
160- if orphans:
161- cancel_deletion = False
162- # All children are orphans
163- for o in orphans:
164- try:
165- tt.new_orphan(o, trans_id)
166- except OrphaningError:
167- # Something bad happened so we cancel the directory
168- # deletion which will leave it in place with a
169- # conflict. The user can deal with it from there.
170- # Note that this also catch the case where we don't
171- # want to create orphans and leave the directory in
172- # place.
173- cancel_deletion = True
174- break
175- if cancel_deletion:
176- # Cancel the directory deletion
177- tt.cancel_deletion(trans_id)
178- new_conflicts.add(('deleting parent', 'Not deleting',
179- trans_id))
180- else:
181- create = True
182- try:
183- tt.final_name(trans_id)
184- except NoFinalPath:
185- if path_tree is not None:
186- file_id = tt.final_file_id(trans_id)
187- if file_id is None:
188- file_id = tt.inactive_file_id(trans_id)
189- _, entry = next(path_tree.iter_entries_by_dir(
190- specific_files=[path_tree.id2path(file_id)]))
191- # special-case the other tree root (move its
192- # children to current root)
193- if entry.parent_id is None:
194- create = False
195- moved = _reparent_transform_children(
196- tt, trans_id, tt.root)
197- for child in moved:
198- new_conflicts.add((c_type, 'Moved to root',
199- child))
200- else:
201- parent_trans_id = tt.trans_id_file_id(
202- entry.parent_id)
203- tt.adjust_path(entry.name, parent_trans_id,
204- trans_id)
205- if create:
206- tt.create_directory(trans_id)
207- new_conflicts.add((c_type, 'Created directory', trans_id))
208- elif c_type == 'unversioned parent':
209- file_id = tt.inactive_file_id(conflict[1])
210- # special-case the other tree root (move its children instead)
211- if path_tree and path_tree.path2id('') == file_id:
212- # This is the root entry, skip it
213- continue
214- tt.version_file(conflict[1], file_id=file_id)
215- new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
216- elif c_type == 'non-directory parent':
217- parent_id = conflict[1]
218- parent_parent = tt.final_parent(parent_id)
219- parent_name = tt.final_name(parent_id)
220- parent_file_id = tt.final_file_id(parent_id)
221- new_parent_id = tt.new_directory(parent_name + '.new',
222- parent_parent, parent_file_id)
223- _reparent_transform_children(tt, parent_id, new_parent_id)
224- if parent_file_id is not None:
225- tt.unversion_file(parent_id)
226- new_conflicts.add((c_type, 'Created directory', new_parent_id))
227- elif c_type == 'versioning no contents':
228- tt.cancel_versioning(conflict[1])
229+ for conflict in conflicts:
230+ resolver = CONFLICT_RESOLVERS.get(conflict[0])
231+ if resolver is None:
232+ continue
233+ new_conflicts.update(resolver(tt, path_tree, *conflict))
234 return new_conflicts
235
236

Subscribers

People subscribed via source and target branches