diff --git a/CHANGELOG.md b/CHANGELOG.md index f837958ae..740194aad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Revset/template aliases now support function overloading. [#2966](https://github.com/martinvonz/jj/issues/2966) +* Conflicted files are individually simplified before being materialized. + ### Fixed bugs ## [0.18.0] - 2024-06-05 diff --git a/cli/tests/test_resolve_command.rs b/cli/tests/test_resolve_command.rs index 297605c1b..086bc92cd 100644 --- a/cli/tests/test_resolve_command.rs +++ b/cli/tests/test_resolve_command.rs @@ -516,38 +516,36 @@ fn test_simplify_conflict_sides() { &[], ); + // Even though the tree-level conflict is a 4-sided conflict, each file is + // materialized as a 2-sided conflict. + insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["debug", "tree"]), + @r###" + fileA: Ok(Conflicted([Some(File { id: FileId("d00491fd7e5bb6fa28c517a0bb32b8b506539d4d"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("0cfbf08886fca9a91cb753ec8734c84fcbe52c9f"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false })])) + fileB: Ok(Conflicted([Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("d00491fd7e5bb6fa28c517a0bb32b8b506539d4d"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("0cfbf08886fca9a91cb753ec8734c84fcbe52c9f"), executable: false })])) + "###); // TODO: The conflict should be simplified before being displayed. insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list"]), @r###" fileA 4-sided conflict fileB 4-sided conflict "###); - // TODO: The conflict should be simplified before being materialized. insta::assert_snapshot!( std::fs::read_to_string(repo_path.join("fileA")).unwrap(), @r###" <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base #1 to side #1 + %%%%%%% Changes from base to side #1 -base +1 +++++++ Contents of side #2 2 - %%%%%%% Changes from base #2 to side #3 - base - %%%%%%% Changes from base #3 to side #4 - base >>>>>>> Conflict 1 of 1 ends "###); insta::assert_snapshot!( std::fs::read_to_string(repo_path.join("fileB")).unwrap(), @r###" <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base #1 to side #1 - base - %%%%%%% Changes from base #2 to side #2 - base - %%%%%%% Changes from base #3 to side #3 + %%%%%%% Changes from base to side #1 -base +1 - +++++++ Contents of side #4 + +++++++ Contents of side #2 2 >>>>>>> Conflict 1 of 1 ends "###); diff --git a/lib/src/conflicts.rs b/lib/src/conflicts.rs index 6bc9bcee7..83754a3b4 100644 --- a/lib/src/conflicts.rs +++ b/lib/src/conflicts.rs @@ -114,6 +114,7 @@ pub async fn materialize( output: &mut dyn Write, ) -> std::io::Result<()> { if let Some(file_merge) = conflict.to_file_merge() { + let file_merge = file_merge.simplify(); let content = extract_as_single_hunk(&file_merge, store, path).await; materialize_merge_result(&content, output) } else { diff --git a/lib/tests/test_local_working_copy.rs b/lib/tests/test_local_working_copy.rs index 8d2de2d28..fe81decf4 100644 --- a/lib/tests/test_local_working_copy.rs +++ b/lib/tests/test_local_working_copy.rs @@ -639,28 +639,28 @@ fn test_materialize_conflicted_files() { } ); - // TODO: Materialized conflicted file should be simplified. + // Even though the tree-level conflict is a 3-sided conflict, each file is + // materialized as a 2-sided conflict. + let file1_value = merged_tree.path_value(file1_path).unwrap(); + let file2_value = merged_tree.path_value(file2_path).unwrap(); + assert_eq!(file1_value.num_sides(), 3); + assert_eq!(file2_value.num_sides(), 3); insta::assert_snapshot!(std::fs::read_to_string(file1_path.to_fs_path(&workspace_root)).ok().unwrap(), @r###" <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base #1 to side #1 - a - %%%%%%% Changes from base #2 to side #2 + %%%%%%% Changes from base to side #1 -b +a - +++++++ Contents of side #3 + +++++++ Contents of side #2 c >>>>>>> Conflict 1 of 1 ends "###); - // TODO: Materialized conflicted file should be simplified. insta::assert_snapshot!(std::fs::read_to_string(file2_path.to_fs_path(&workspace_root)).ok().unwrap(), @r###" <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base #1 to side #1 + %%%%%%% Changes from base to side #1 -2 +1 +++++++ Contents of side #2 4 - %%%%%%% Changes from base #2 to side #3 - 3 >>>>>>> Conflict 1 of 1 ends "###); }