Skip to content

Commit 1f3cf4a

Browse files
committed
merge-tools builtin: correctly set executable bit on binary files with selected changes
When splitting a commit which both changes a binary file and flips the executable bit, selecting just the `Binary` change but not the `FileMode` change still applied the mode change. This is now fixed by overriding the file mode of a selected binary file with whatever file mode scm-record tells us was selected.
1 parent aebf681 commit 1f3cf4a

File tree

1 file changed

+154
-2
lines changed

1 file changed

+154
-2
lines changed

cli/src/merge_tools/builtin.rs

Lines changed: 154 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,9 +477,17 @@ fn apply_changes(
477477
}
478478
scm_record::SelectedContents::Binary {
479479
old_description: _,
480-
new_description: _,
480+
new_description: Some(_),
481481
} => {
482-
let value = select_right(&path)?;
482+
let value = override_file_executable_bit(select_right(&path)?, executable);
483+
tree_builder.set_or_remove(path, value);
484+
}
485+
scm_record::SelectedContents::Binary {
486+
old_description: _,
487+
new_description: None,
488+
} => {
489+
// File contents emptied out, but file mode is not absent => write empty file.
490+
let value = write_file(&path, &[], executable)?;
483491
tree_builder.set_or_remove(path, value);
484492
}
485493
scm_record::SelectedContents::Text { contents } => {
@@ -1177,6 +1185,150 @@ mod tests {
11771185
);
11781186
}
11791187

1188+
#[test]
1189+
fn test_edit_diff_builtin_change_binary_file_with_unselected_file_mode_change() {
1190+
let test_repo = TestRepo::init();
1191+
let store = test_repo.repo.store();
1192+
1193+
let binary_file_path = repo_path("binary_file");
1194+
let left_tree = testutils::create_tree_with(&test_repo.repo, |builder| {
1195+
builder
1196+
.file(binary_file_path, vec![0xff, 0x00])
1197+
.executable(false);
1198+
});
1199+
let right_tree = testutils::create_tree_with(&test_repo.repo, |builder| {
1200+
builder
1201+
.file(binary_file_path, vec![0xff, 0x01])
1202+
.executable(true);
1203+
});
1204+
1205+
let (changed_files, files) = make_diff(store, &left_tree, &right_tree);
1206+
insta::assert_debug_snapshot!(changed_files, @r#"
1207+
[
1208+
"binary_file",
1209+
]
1210+
"#);
1211+
insta::assert_debug_snapshot!(files, @r#"
1212+
[
1213+
File {
1214+
old_path: None,
1215+
path: "binary_file",
1216+
file_mode: Unix(
1217+
33188,
1218+
),
1219+
sections: [
1220+
FileMode {
1221+
is_checked: false,
1222+
mode: Unix(
1223+
33261,
1224+
),
1225+
},
1226+
Binary {
1227+
is_checked: false,
1228+
old_description: Some(
1229+
"fb296c879f1852c0dca0 (2B)",
1230+
),
1231+
new_description: Some(
1232+
"cc429d26cbaec338223b (2B)",
1233+
),
1234+
},
1235+
],
1236+
},
1237+
]
1238+
"#);
1239+
1240+
// Select only the binary change
1241+
let mut files = files;
1242+
for file in &mut files {
1243+
for section in &mut file.sections {
1244+
if let scm_record::Section::Binary { is_checked, .. } = section {
1245+
*is_checked = true;
1246+
}
1247+
}
1248+
}
1249+
1250+
let expected_tree = testutils::create_tree_with(&test_repo.repo, |builder| {
1251+
builder
1252+
.file(binary_file_path, vec![0xff, 0x01])
1253+
.executable(false);
1254+
});
1255+
let actual_tree_id = apply_diff(store, &left_tree, &right_tree, &changed_files, &files);
1256+
let actual_tree = store.get_root_tree(&actual_tree_id).unwrap();
1257+
assert_eq!(
1258+
expected_tree.id(),
1259+
actual_tree.id(),
1260+
"tree was different:\nexpected tree:\n{}\nactual tree:\n{}",
1261+
dump_tree(store, &expected_tree.id()),
1262+
dump_tree(store, &actual_tree.id()),
1263+
);
1264+
}
1265+
1266+
#[test]
1267+
fn test_edit_diff_builtin_delete_binary_file_with_unselected_file_mode_change() {
1268+
let test_repo = TestRepo::init();
1269+
let store = test_repo.repo.store();
1270+
1271+
let binary_file_path = repo_path("binary_file");
1272+
let left_tree = testutils::create_tree_with(&test_repo.repo, |builder| {
1273+
builder.file(binary_file_path, vec![0xff, 0x00]);
1274+
});
1275+
let right_tree = testutils::create_tree(&test_repo.repo, &[]);
1276+
1277+
let (changed_files, files) = make_diff(store, &left_tree, &right_tree);
1278+
insta::assert_debug_snapshot!(changed_files, @r#"
1279+
[
1280+
"binary_file",
1281+
]
1282+
"#);
1283+
insta::assert_debug_snapshot!(files, @r#"
1284+
[
1285+
File {
1286+
old_path: None,
1287+
path: "binary_file",
1288+
file_mode: Unix(
1289+
33188,
1290+
),
1291+
sections: [
1292+
FileMode {
1293+
is_checked: false,
1294+
mode: Absent,
1295+
},
1296+
Binary {
1297+
is_checked: false,
1298+
old_description: Some(
1299+
"fb296c879f1852c0dca0 (2B)",
1300+
),
1301+
new_description: None,
1302+
},
1303+
],
1304+
},
1305+
]
1306+
"#);
1307+
1308+
// Select only the binary change
1309+
let mut files = files;
1310+
for file in &mut files {
1311+
for section in &mut file.sections {
1312+
if let scm_record::Section::Binary { is_checked, .. } = section {
1313+
*is_checked = true;
1314+
}
1315+
}
1316+
}
1317+
1318+
let expected_tree = testutils::create_tree_with(&test_repo.repo, |builder| {
1319+
builder.file(binary_file_path, vec![]);
1320+
});
1321+
let actual_tree_id = apply_diff(store, &left_tree, &right_tree, &changed_files, &files);
1322+
let actual_tree = store.get_root_tree(&actual_tree_id).unwrap();
1323+
assert_eq!(
1324+
expected_tree.id(),
1325+
actual_tree.id(),
1326+
"tree was different:\nexpected tree:\n{}\nactual tree:\n{}",
1327+
dump_tree(store, &expected_tree.id()),
1328+
dump_tree(store, &actual_tree.id()),
1329+
);
1330+
}
1331+
11801332
#[test]
11811333
fn test_edit_diff_builtin_delete_file() {
11821334
let test_repo = TestRepo::init();

0 commit comments

Comments
 (0)