1087
1090
elif target_kind is not None:
1088
1091
raise AssertionError(target_kind)
1089
1092
if not wt_versioned and target_versioned:
1090
tt.version_file(trans_id, file_id=file_id)
1094
trans_id, file_id=getattr(change, 'file_id', None))
1091
1095
if wt_versioned and not target_versioned:
1092
1096
tt.unversion_file(trans_id)
1093
1097
if (target_name is not None
1094
and (wt_name != target_name or wt_parent != target_parent)):
1095
if target_name == '' and target_parent is None:
1098
and (wt_name != target_name or change.is_reparented())):
1099
if target_path == '':
1096
1100
parent_trans = ROOT_PARENT
1098
1102
parent_trans = tt.trans_id_file_id(target_parent)
1099
if wt_parent is None and wt_versioned:
1103
if wt_path == '' and wt_versioned:
1100
1104
tt.adjust_root_path(target_name, parent_trans)
1102
1106
tt.adjust_path(target_name, parent_trans, trans_id)
1103
1107
if wt_executable != target_executable and target_kind == "file":
1104
1108
tt.set_executability(target_executable, trans_id)
1105
1109
if working_tree.supports_content_filtering():
1106
for (trans_id, mode_id, file_id), bytes in (
1110
for (trans_id, mode_id, target_path), bytes in (
1107
1111
target_tree.iter_files_bytes(deferred_files)):
1108
1112
# We're reverting a tree to the target tree so using the
1109
1113
# target tree to find the file path seems the best choice
1110
1114
# here IMO - Ian C 27/Oct/2009
1111
filter_tree_path = target_tree.id2path(file_id)
1112
filters = working_tree._content_filter_stack(filter_tree_path)
1115
filters = working_tree._content_filter_stack(target_path)
1113
1116
bytes = filtered_output_bytes(
1114
1117
bytes, filters,
1115
ContentFilterContext(filter_tree_path, working_tree))
1118
ContentFilterContext(target_path, working_tree))
1116
1119
tt.create_file(bytes, trans_id, mode_id)
1118
for (trans_id, mode_id, file_id), bytes in target_tree.iter_files_bytes(
1121
for (trans_id, mode_id, target_path), bytes in target_tree.iter_files_bytes(
1119
1122
deferred_files):
1120
1123
tt.create_file(bytes, trans_id, mode_id)
1121
1124
tt.fixup_new_roots()
1133
1136
with ui.ui_factory.nested_progress_bar() as pb:
1134
1137
for n in range(10):
1135
1138
pb.update(gettext('Resolution pass'), n + 1, 10)
1136
conflicts = tt.find_conflicts()
1139
conflicts = tt.find_raw_conflicts()
1137
1140
if len(conflicts) == 0:
1138
1141
return new_conflicts
1139
1142
new_conflicts.update(pass_func(tt, conflicts))
1140
1143
raise MalformedTransform(conflicts=conflicts)
1146
def resolve_duplicate_id(tt, path_tree, c_type, old_trans_id, trans_id):
1147
tt.unversion_file(old_trans_id)
1148
yield (c_type, 'Unversioned existing file', old_trans_id, trans_id)
1151
def resolve_duplicate(tt, path_tree, c_type, last_trans_id, trans_id, name):
1152
# files that were renamed take precedence
1153
final_parent = tt.final_parent(last_trans_id)
1154
if tt.path_changed(last_trans_id):
1155
existing_file, new_file = trans_id, last_trans_id
1157
existing_file, new_file = last_trans_id, trans_id
1158
new_name = tt.final_name(existing_file) + '.moved'
1159
tt.adjust_path(new_name, final_parent, existing_file)
1160
yield (c_type, 'Moved existing file to', existing_file, new_file)
1163
def resolve_parent_loop(tt, path_tree, c_type, cur):
1164
# break the loop by undoing one of the ops that caused the loop
1165
while not tt.path_changed(cur):
1166
cur = tt.final_parent(cur)
1167
yield (c_type, 'Cancelled move', cur, tt.final_parent(cur),)
1168
tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
1171
def resolve_missing_parent(tt, path_tree, c_type, trans_id):
1172
if trans_id in tt._removed_contents:
1173
cancel_deletion = True
1174
orphans = tt._get_potential_orphans(trans_id)
1176
cancel_deletion = False
1177
# All children are orphans
1180
tt.new_orphan(o, trans_id)
1181
except OrphaningError:
1182
# Something bad happened so we cancel the directory
1183
# deletion which will leave it in place with a
1184
# conflict. The user can deal with it from there.
1185
# Note that this also catch the case where we don't
1186
# want to create orphans and leave the directory in
1188
cancel_deletion = True
1191
# Cancel the directory deletion
1192
tt.cancel_deletion(trans_id)
1193
yield ('deleting parent', 'Not deleting', trans_id)
1197
tt.final_name(trans_id)
1199
if path_tree is not None:
1200
file_id = tt.final_file_id(trans_id)
1202
file_id = tt.inactive_file_id(trans_id)
1203
_, entry = next(path_tree.iter_entries_by_dir(
1204
specific_files=[path_tree.id2path(file_id)]))
1205
# special-case the other tree root (move its
1206
# children to current root)
1207
if entry.parent_id is None:
1209
moved = _reparent_transform_children(
1210
tt, trans_id, tt.root)
1212
yield (c_type, 'Moved to root', child)
1214
parent_trans_id = tt.trans_id_file_id(
1216
tt.adjust_path(entry.name, parent_trans_id,
1219
tt.create_directory(trans_id)
1220
yield (c_type, 'Created directory', trans_id)
1223
def resolve_unversioned_parent(tt, path_tree, c_type, trans_id):
1224
file_id = tt.inactive_file_id(trans_id)
1225
# special-case the other tree root (move its children instead)
1226
if path_tree and path_tree.path2id('') == file_id:
1227
# This is the root entry, skip it
1229
tt.version_file(trans_id, file_id=file_id)
1230
yield (c_type, 'Versioned directory', trans_id)
1233
def resolve_non_directory_parent(tt, path_tree, c_type, parent_id):
1234
parent_parent = tt.final_parent(parent_id)
1235
parent_name = tt.final_name(parent_id)
1236
# TODO(jelmer): Make this code transform-specific
1237
if tt._tree.supports_setting_file_ids():
1238
parent_file_id = tt.final_file_id(parent_id)
1240
parent_file_id = b'DUMMY'
1241
new_parent_id = tt.new_directory(parent_name + '.new',
1242
parent_parent, parent_file_id)
1243
_reparent_transform_children(tt, parent_id, new_parent_id)
1244
if parent_file_id is not None:
1245
tt.unversion_file(parent_id)
1246
yield (c_type, 'Created directory', new_parent_id)
1249
def resolve_versioning_no_contents(tt, path_tree, c_type, trans_id):
1250
tt.cancel_versioning(trans_id)
1254
CONFLICT_RESOLVERS = {
1255
'duplicate id': resolve_duplicate_id,
1256
'duplicate': resolve_duplicate,
1257
'parent loop': resolve_parent_loop,
1258
'missing parent': resolve_missing_parent,
1259
'unversioned parent': resolve_unversioned_parent,
1260
'non-directory parent': resolve_non_directory_parent,
1261
'versioning no contents': resolve_versioning_no_contents,
1143
1265
def conflict_pass(tt, conflicts, path_tree=None):
1144
1266
"""Resolve some classes of conflicts.
1148
1270
:param path_tree: A Tree to get supplemental paths from
1150
1272
new_conflicts = set()
1151
for c_type, conflict in ((c[0], c) for c in conflicts):
1152
if c_type == 'duplicate id':
1153
tt.unversion_file(conflict[1])
1154
new_conflicts.add((c_type, 'Unversioned existing file',
1155
conflict[1], conflict[2], ))
1156
elif c_type == 'duplicate':
1157
# files that were renamed take precedence
1158
final_parent = tt.final_parent(conflict[1])
1159
if tt.path_changed(conflict[1]):
1160
existing_file, new_file = conflict[2], conflict[1]
1162
existing_file, new_file = conflict[1], conflict[2]
1163
new_name = tt.final_name(existing_file) + '.moved'
1164
tt.adjust_path(new_name, final_parent, existing_file)
1165
new_conflicts.add((c_type, 'Moved existing file to',
1166
existing_file, new_file))
1167
elif c_type == 'parent loop':
1168
# break the loop by undoing one of the ops that caused the loop
1170
while not tt.path_changed(cur):
1171
cur = tt.final_parent(cur)
1172
new_conflicts.add((c_type, 'Cancelled move', cur,
1173
tt.final_parent(cur),))
1174
tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
1176
elif c_type == 'missing parent':
1177
trans_id = conflict[1]
1178
if trans_id in tt._removed_contents:
1179
cancel_deletion = True
1180
orphans = tt._get_potential_orphans(trans_id)
1182
cancel_deletion = False
1183
# All children are orphans
1186
tt.new_orphan(o, trans_id)
1187
except OrphaningError:
1188
# Something bad happened so we cancel the directory
1189
# deletion which will leave it in place with a
1190
# conflict. The user can deal with it from there.
1191
# Note that this also catch the case where we don't
1192
# want to create orphans and leave the directory in
1194
cancel_deletion = True
1197
# Cancel the directory deletion
1198
tt.cancel_deletion(trans_id)
1199
new_conflicts.add(('deleting parent', 'Not deleting',
1204
tt.final_name(trans_id)
1206
if path_tree is not None:
1207
file_id = tt.final_file_id(trans_id)
1209
file_id = tt.inactive_file_id(trans_id)
1210
_, entry = next(path_tree.iter_entries_by_dir(
1211
specific_files=[path_tree.id2path(file_id)]))
1212
# special-case the other tree root (move its
1213
# children to current root)
1214
if entry.parent_id is None:
1216
moved = _reparent_transform_children(
1217
tt, trans_id, tt.root)
1219
new_conflicts.add((c_type, 'Moved to root',
1222
parent_trans_id = tt.trans_id_file_id(
1224
tt.adjust_path(entry.name, parent_trans_id,
1227
tt.create_directory(trans_id)
1228
new_conflicts.add((c_type, 'Created directory', trans_id))
1229
elif c_type == 'unversioned parent':
1230
file_id = tt.inactive_file_id(conflict[1])
1231
# special-case the other tree root (move its children instead)
1232
if path_tree and path_tree.path2id('') == file_id:
1233
# This is the root entry, skip it
1235
tt.version_file(conflict[1], file_id=file_id)
1236
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1237
elif c_type == 'non-directory parent':
1238
parent_id = conflict[1]
1239
parent_parent = tt.final_parent(parent_id)
1240
parent_name = tt.final_name(parent_id)
1241
parent_file_id = tt.final_file_id(parent_id)
1242
new_parent_id = tt.new_directory(parent_name + '.new',
1243
parent_parent, parent_file_id)
1244
_reparent_transform_children(tt, parent_id, new_parent_id)
1245
if parent_file_id is not None:
1246
tt.unversion_file(parent_id)
1247
new_conflicts.add((c_type, 'Created directory', new_parent_id))
1248
elif c_type == 'versioning no contents':
1249
tt.cancel_versioning(conflict[1])
1273
for conflict in conflicts:
1274
resolver = CONFLICT_RESOLVERS.get(conflict[0])
1275
if resolver is None:
1277
new_conflicts.update(resolver(tt, path_tree, *conflict))
1250
1278
return new_conflicts
1253
def cook_conflicts(raw_conflicts, tt):
1254
"""Generate a list of cooked conflicts, sorted by file path"""
1255
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1256
return sorted(conflict_iter, key=conflicts.Conflict.sort_key)
1259
def iter_cook_conflicts(raw_conflicts, tt):
1261
for conflict in raw_conflicts:
1262
c_type = conflict[0]
1263
action = conflict[1]
1264
modified_path = fp.get_path(conflict[2])
1265
modified_id = tt.final_file_id(conflict[2])
1266
if len(conflict) == 3:
1267
yield conflicts.Conflict.factory(
1268
c_type, action=action, path=modified_path, file_id=modified_id)
1271
conflicting_path = fp.get_path(conflict[3])
1272
conflicting_id = tt.final_file_id(conflict[3])
1273
yield conflicts.Conflict.factory(
1274
c_type, action=action, path=modified_path,
1275
file_id=modified_id,
1276
conflict_path=conflicting_path,
1277
conflict_file_id=conflicting_id)
1280
1281
class _FileMover(object):
1281
1282
"""Moves and deletes files for TreeTransform, tracking operations"""