Skip to content

Commit fa00775

Browse files
committed
cli: resolve: do not use .with_new_file_ids() to construct resolved file value
This reverts a01d0bf "cli: resolve: leave executable bit unchanged when using external tool", and updates handling of resolved content. Fixes #6250
1 parent ace0a52 commit fa00775

File tree

4 files changed

+133
-23
lines changed

4 files changed

+133
-23
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4040

4141
### Fixed bugs
4242

43+
* Fixed crash on change-delete conflict resolution.
44+
[#6250](https://github.com/jj-vcs/jj/issues/6250)
45+
4346
### Packaging changes
4447

4548
* Jujutsu now uses

cli/src/merge_tools/external.rs

+9-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::sync::Arc;
99
use bstr::BString;
1010
use itertools::Itertools as _;
1111
use jj_lib::backend::MergedTreeId;
12+
use jj_lib::backend::TreeValue;
1213
use jj_lib::conflicts;
1314
use jj_lib::conflicts::choose_materialized_conflict_marker_len;
1415
use jj_lib::conflicts::materialize_merge_result_to_bytes_with_marker_len;
@@ -308,13 +309,15 @@ fn run_mergetool_external_single_file(
308309
ExternalToolError::InvalidConflictMarkers { exit_status },
309310
));
310311
}
311-
// Update the file ids only, leaving the executable flags unchanged
312-
let new_file_ids = if let Some(resolved) = new_file_ids.as_resolved() {
313-
Merge::from_vec(vec![resolved.clone(); conflict.as_slice().len()])
314-
} else {
315-
new_file_ids
312+
313+
let new_tree_value = match new_file_ids.into_resolved() {
314+
Ok(file_id) => {
315+
let executable = file.executable.expect("should have been resolved");
316+
Merge::resolved(file_id.map(|id| TreeValue::File { id, executable }))
317+
}
318+
// Update the file ids only, leaving the executable flags unchanged
319+
Err(file_ids) => conflict.with_new_file_ids(&file_ids),
316320
};
317-
let new_tree_value = conflict.with_new_file_ids(&new_file_ids);
318321
tree_builder.set_or_remove(repo_path.to_owned(), new_tree_value);
319322
Ok(())
320323
}

cli/src/merge_tools/mod.rs

+6-11
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use std::sync::Arc;
2121
use itertools::Itertools as _;
2222
use jj_lib::backend::BackendError;
2323
use jj_lib::backend::MergedTreeId;
24+
use jj_lib::backend::TreeValue;
2425
use jj_lib::config::ConfigGetError;
2526
use jj_lib::config::ConfigGetResultExt as _;
2627
use jj_lib::config::ConfigNamePathBuf;
@@ -460,17 +461,11 @@ fn pick_conflict_side(
460461
for merge_tool_file in merge_tool_files {
461462
// We use file IDs here to match the logic for the other external merge tools.
462463
// This ensures that the behavior is consistent.
463-
let file_id = merge_tool_file.file.ids.get_add(add_index).unwrap();
464-
// Update the file IDs only, leaving the executable flags unchanged
465-
let new_tree_value = merge_tool_file
466-
.conflict
467-
.with_new_file_ids(&Merge::from_vec(vec![
468-
file_id.clone();
469-
merge_tool_file
470-
.conflict
471-
.as_slice()
472-
.len()
473-
]));
464+
let file = &merge_tool_file.file;
465+
let file_id = file.ids.get_add(add_index).unwrap();
466+
let executable = file.executable.expect("should have been resolved");
467+
let new_tree_value =
468+
Merge::resolved(file_id.clone().map(|id| TreeValue::File { id, executable }));
474469
tree_builder.set_or_remove(merge_tool_file.repo_path.clone(), new_tree_value);
475470
}
476471
tree_builder.write_tree(tree.store())

cli/tests/test_resolve_command.rs

+115-6
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,86 @@ fn test_resolve_conflicts_with_executable() {
10481048
file1 2-sided conflict including an executable
10491049
[EOF]
10501050
");
1051+
1052+
// Pick "our" contents, but merges executable bits
1053+
work_dir.run_jj(["undo"]).success();
1054+
let output = work_dir.run_jj(["resolve", "--tool=:ours"]);
1055+
insta::assert_snapshot!(output, @r"
1056+
------- stderr -------
1057+
Working copy (@) now at: znkkpsqq 5b59d7f0 conflict | conflict
1058+
Parent commit (@-) : mzvwutvl 08932848 a | a
1059+
Parent commit (@-) : yqosqzyt b69b3de6 b | b
1060+
Added 0 files, modified 2 files, removed 0 files
1061+
[EOF]
1062+
");
1063+
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
1064+
diff --git a/file1 b/file1
1065+
index 0000000000..da0f8ed91a 100755
1066+
--- a/file1
1067+
+++ b/file1
1068+
@@ -1,7 +1,1 @@
1069+
-<<<<<<< Conflict 1 of 1
1070+
-%%%%%%% Changes from base to side #1
1071+
--base1
1072+
-+a1
1073+
-+++++++ Contents of side #2
1074+
-b1
1075+
->>>>>>> Conflict 1 of 1 ends
1076+
+a1
1077+
diff --git a/file2 b/file2
1078+
index 0000000000..c1827f07e1 100755
1079+
--- a/file2
1080+
+++ b/file2
1081+
@@ -1,7 +1,1 @@
1082+
-<<<<<<< Conflict 1 of 1
1083+
-%%%%%%% Changes from base to side #1
1084+
--base2
1085+
-+a2
1086+
-+++++++ Contents of side #2
1087+
-b2
1088+
->>>>>>> Conflict 1 of 1 ends
1089+
+a2
1090+
[EOF]
1091+
");
1092+
1093+
// Pick "their" contents, but merges executable bits
1094+
work_dir.run_jj(["undo"]).success();
1095+
let output = work_dir.run_jj(["resolve", "--tool=:theirs"]);
1096+
insta::assert_snapshot!(output, @r"
1097+
------- stderr -------
1098+
Working copy (@) now at: znkkpsqq 087b8637 conflict | conflict
1099+
Parent commit (@-) : mzvwutvl 08932848 a | a
1100+
Parent commit (@-) : yqosqzyt b69b3de6 b | b
1101+
Added 0 files, modified 2 files, removed 0 files
1102+
[EOF]
1103+
");
1104+
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
1105+
diff --git a/file1 b/file1
1106+
index 0000000000..c9c6af7f78 100755
1107+
--- a/file1
1108+
+++ b/file1
1109+
@@ -1,7 +1,1 @@
1110+
-<<<<<<< Conflict 1 of 1
1111+
-%%%%%%% Changes from base to side #1
1112+
--base1
1113+
-+a1
1114+
-+++++++ Contents of side #2
1115+
b1
1116+
->>>>>>> Conflict 1 of 1 ends
1117+
diff --git a/file2 b/file2
1118+
index 0000000000..e6bfff5c1d 100755
1119+
--- a/file2
1120+
+++ b/file2
1121+
@@ -1,7 +1,1 @@
1122+
-<<<<<<< Conflict 1 of 1
1123+
-%%%%%%% Changes from base to side #1
1124+
--base2
1125+
-+a2
1126+
-+++++++ Contents of side #2
1127+
b2
1128+
->>>>>>> Conflict 1 of 1 ends
1129+
[EOF]
1130+
");
10511131
}
10521132

10531133
#[test]
@@ -1213,7 +1293,19 @@ fn test_resolve_change_delete_executable() {
12131293
insta::assert_snapshot!(output, @"");
12141294
std::fs::write(&editor_script, "write\nresolved\n").unwrap();
12151295
let output = work_dir.run_jj(["resolve", "file2"]);
1216-
assert_eq!(output.status.code(), Some(101)); // FIXME
1296+
insta::assert_snapshot!(output, @r"
1297+
------- stderr -------
1298+
Resolving conflicts in: file2
1299+
Working copy (@) now at: kmkuslsw cab8be9b conflict | (conflict) conflict
1300+
Parent commit (@-) : mzvwutvl 4a44e1a9 a | a
1301+
Parent commit (@-) : vruxwmqv 19e7d2ff b | b
1302+
Added 0 files, modified 1 files, removed 0 files
1303+
Warning: There are unresolved conflicts at these paths:
1304+
file3 2-sided conflict including an executable
1305+
file4 2-sided conflict including 1 deletion
1306+
file5 2-sided conflict including 1 deletion and an executable
1307+
[EOF]
1308+
");
12171309

12181310
// Exec bit conflict can be resolved by chmod
12191311
let output = work_dir.run_jj(["resolve", "file3"]);
@@ -1233,17 +1325,34 @@ fn test_resolve_change_delete_executable() {
12331325

12341326
// Take modified content, the executable bit should be kept as "-"
12351327
let output = work_dir.run_jj(["resolve", "file4", "--tool=:ours"]);
1236-
assert_eq!(output.status.code(), Some(101)); // FIXME
1328+
insta::assert_snapshot!(output, @r"
1329+
------- stderr -------
1330+
Working copy (@) now at: kmkuslsw 14b5c3d2 conflict | (conflict) conflict
1331+
Parent commit (@-) : mzvwutvl 4a44e1a9 a | a
1332+
Parent commit (@-) : vruxwmqv 19e7d2ff b | b
1333+
Added 0 files, modified 1 files, removed 0 files
1334+
Warning: There are unresolved conflicts at these paths:
1335+
file5 2-sided conflict including 1 deletion and an executable
1336+
[EOF]
1337+
");
12371338

12381339
// Take modified content, the executable bit should be kept as "x"
12391340
let output = work_dir.run_jj(["resolve", "file5", "--tool=:theirs"]);
1240-
assert_eq!(output.status.code(), Some(101)); // FIXME
1341+
insta::assert_snapshot!(output, @r"
1342+
------- stderr -------
1343+
Working copy (@) now at: kmkuslsw 5de68577 conflict | conflict
1344+
Parent commit (@-) : mzvwutvl 4a44e1a9 a | a
1345+
Parent commit (@-) : vruxwmqv 19e7d2ff b | b
1346+
Added 0 files, modified 1 files, removed 0 files
1347+
Existing conflicts were resolved or abandoned from 1 commits.
1348+
[EOF]
1349+
");
12411350

12421351
insta::assert_snapshot!(file_list("all()"), @r"
1243-
file2 c -
1352+
file2 - -
12441353
file3 - x
1245-
file4 c -
1246-
file5 c x
1354+
file4 - -
1355+
file5 - x
12471356
[EOF]
12481357
");
12491358
}

0 commit comments

Comments
 (0)