1146
1146
raise MalformedTransform(conflicts=conflicts)
1149
def resolve_duplicate_id(tt, path_tree, c_type, old_trans_id, trans_id):
1150
tt.unversion_file(old_trans_id)
1151
yield (c_type, 'Unversioned existing file', old_trans_id, trans_id)
1154
def resolve_duplicate(tt, path_tree, c_type, last_trans_id, trans_id, name):
1155
# files that were renamed take precedence
1156
final_parent = tt.final_parent(last_trans_id)
1157
if tt.path_changed(last_trans_id):
1158
existing_file, new_file = trans_id, last_trans_id
1160
existing_file, new_file = last_trans_id, trans_id
1161
new_name = tt.final_name(existing_file) + '.moved'
1162
tt.adjust_path(new_name, final_parent, existing_file)
1163
yield (c_type, 'Moved existing file to', existing_file, new_file)
1166
def resolve_parent_loop(tt, path_tree, c_type, cur):
1167
# break the loop by undoing one of the ops that caused the loop
1168
while not tt.path_changed(cur):
1169
cur = tt.final_parent(cur)
1170
yield (c_type, 'Cancelled move', cur, tt.final_parent(cur),)
1171
tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
1174
def resolve_missing_parent(tt, path_tree, c_type, trans_id):
1175
if trans_id in tt._removed_contents:
1176
cancel_deletion = True
1177
orphans = tt._get_potential_orphans(trans_id)
1179
cancel_deletion = False
1180
# All children are orphans
1183
tt.new_orphan(o, trans_id)
1184
except OrphaningError:
1185
# Something bad happened so we cancel the directory
1186
# deletion which will leave it in place with a
1187
# conflict. The user can deal with it from there.
1188
# Note that this also catch the case where we don't
1189
# want to create orphans and leave the directory in
1191
cancel_deletion = True
1194
# Cancel the directory deletion
1195
tt.cancel_deletion(trans_id)
1196
yield ('deleting parent', 'Not deleting', trans_id)
1200
tt.final_name(trans_id)
1202
if path_tree is not None:
1203
file_id = tt.final_file_id(trans_id)
1205
file_id = tt.inactive_file_id(trans_id)
1206
_, entry = next(path_tree.iter_entries_by_dir(
1207
specific_files=[path_tree.id2path(file_id)]))
1208
# special-case the other tree root (move its
1209
# children to current root)
1210
if entry.parent_id is None:
1212
moved = _reparent_transform_children(
1213
tt, trans_id, tt.root)
1215
yield (c_type, 'Moved to root', child)
1217
parent_trans_id = tt.trans_id_file_id(
1219
tt.adjust_path(entry.name, parent_trans_id,
1222
tt.create_directory(trans_id)
1223
yield (c_type, 'Created directory', trans_id)
1226
def resolve_unversioned_parent(tt, path_tree, c_type, trans_id):
1227
file_id = tt.inactive_file_id(trans_id)
1228
# special-case the other tree root (move its children instead)
1229
if path_tree and path_tree.path2id('') == file_id:
1230
# This is the root entry, skip it
1232
tt.version_file(trans_id, file_id=file_id)
1233
yield (c_type, 'Versioned directory', trans_id)
1236
def resolve_non_directory_parent(tt, path_tree, c_type, parent_id):
1237
parent_parent = tt.final_parent(parent_id)
1238
parent_name = tt.final_name(parent_id)
1239
parent_file_id = tt.final_file_id(parent_id)
1240
new_parent_id = tt.new_directory(parent_name + '.new',
1241
parent_parent, parent_file_id)
1242
_reparent_transform_children(tt, parent_id, new_parent_id)
1243
if parent_file_id is not None:
1244
tt.unversion_file(parent_id)
1245
yield (c_type, 'Created directory', new_parent_id)
1248
def resolve_versioning_no_contents(tt, path_tree, c_type, trans_id):
1249
tt.cancel_versioning(trans_id)
1253
CONFLICT_RESOLVERS = {
1254
'duplicate id': resolve_duplicate_id,
1255
'duplicate': resolve_duplicate,
1256
'parent loop': resolve_parent_loop,
1257
'missing parent': resolve_missing_parent,
1258
'unversioned parent': resolve_unversioned_parent,
1259
'non-directory parent': resolve_non_directory_parent,
1260
'versioning no contents': resolve_versioning_no_contents,
1149
1264
def conflict_pass(tt, conflicts, path_tree=None):
1150
1265
"""Resolve some classes of conflicts.
1154
1269
:param path_tree: A Tree to get supplemental paths from
1156
1271
new_conflicts = set()
1157
for c_type, conflict in ((c[0], c) for c in conflicts):
1158
if c_type == 'duplicate id':
1159
tt.unversion_file(conflict[1])
1160
new_conflicts.add((c_type, 'Unversioned existing file',
1161
conflict[1], conflict[2], ))
1162
elif c_type == 'duplicate':
1163
# files that were renamed take precedence
1164
final_parent = tt.final_parent(conflict[1])
1165
if tt.path_changed(conflict[1]):
1166
existing_file, new_file = conflict[2], conflict[1]
1168
existing_file, new_file = conflict[1], conflict[2]
1169
new_name = tt.final_name(existing_file) + '.moved'
1170
tt.adjust_path(new_name, final_parent, existing_file)
1171
new_conflicts.add((c_type, 'Moved existing file to',
1172
existing_file, new_file))
1173
elif c_type == 'parent loop':
1174
# break the loop by undoing one of the ops that caused the loop
1176
while not tt.path_changed(cur):
1177
cur = tt.final_parent(cur)
1178
new_conflicts.add((c_type, 'Cancelled move', cur,
1179
tt.final_parent(cur),))
1180
tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
1182
elif c_type == 'missing parent':
1183
trans_id = conflict[1]
1184
if trans_id in tt._removed_contents:
1185
cancel_deletion = True
1186
orphans = tt._get_potential_orphans(trans_id)
1188
cancel_deletion = False
1189
# All children are orphans
1192
tt.new_orphan(o, trans_id)
1193
except OrphaningError:
1194
# Something bad happened so we cancel the directory
1195
# deletion which will leave it in place with a
1196
# conflict. The user can deal with it from there.
1197
# Note that this also catch the case where we don't
1198
# want to create orphans and leave the directory in
1200
cancel_deletion = True
1203
# Cancel the directory deletion
1204
tt.cancel_deletion(trans_id)
1205
new_conflicts.add(('deleting parent', 'Not deleting',
1210
tt.final_name(trans_id)
1212
if path_tree is not None:
1213
file_id = tt.final_file_id(trans_id)
1215
file_id = tt.inactive_file_id(trans_id)
1216
_, entry = next(path_tree.iter_entries_by_dir(
1217
specific_files=[path_tree.id2path(file_id)]))
1218
# special-case the other tree root (move its
1219
# children to current root)
1220
if entry.parent_id is None:
1222
moved = _reparent_transform_children(
1223
tt, trans_id, tt.root)
1225
new_conflicts.add((c_type, 'Moved to root',
1228
parent_trans_id = tt.trans_id_file_id(
1230
tt.adjust_path(entry.name, parent_trans_id,
1233
tt.create_directory(trans_id)
1234
new_conflicts.add((c_type, 'Created directory', trans_id))
1235
elif c_type == 'unversioned parent':
1236
file_id = tt.inactive_file_id(conflict[1])
1237
# special-case the other tree root (move its children instead)
1238
if path_tree and path_tree.path2id('') == file_id:
1239
# This is the root entry, skip it
1241
tt.version_file(conflict[1], file_id=file_id)
1242
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1243
elif c_type == 'non-directory parent':
1244
parent_id = conflict[1]
1245
parent_parent = tt.final_parent(parent_id)
1246
parent_name = tt.final_name(parent_id)
1247
parent_file_id = tt.final_file_id(parent_id)
1248
new_parent_id = tt.new_directory(parent_name + '.new',
1249
parent_parent, parent_file_id)
1250
_reparent_transform_children(tt, parent_id, new_parent_id)
1251
if parent_file_id is not None:
1252
tt.unversion_file(parent_id)
1253
new_conflicts.add((c_type, 'Created directory', new_parent_id))
1254
elif c_type == 'versioning no contents':
1255
tt.cancel_versioning(conflict[1])
1272
for conflict in conflicts:
1273
resolver = CONFLICT_RESOLVERS.get(conflict[0])
1274
if resolver is None:
1276
new_conflicts.update(resolver(tt, path_tree, *conflict))
1256
1277
return new_conflicts