// Copyright 2021 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use jj_lib::backend::FileId;use jj_lib::conflicts::{ extract_as_single_hunk, materialize_merge_result, parse_conflict, update_from_content,};use jj_lib::merge::Merge;use jj_lib::repo::Repo;use jj_lib::repo_path::RepoPath;use jj_lib::store::Store;use pollster::FutureExt;use testutils::TestRepo; #[test]fn test_materialize_conflict_basic() { let test_repo = TestRepo::init(); let store = test_repo.repo.store(); let path = RepoPath::from_internal_string("file"); let base_id = testutils::write_file( store, path, "line 1line 2line 3line 4line 5", ); let left_id = testutils::write_file( store, path, "line 1line 2left 3.1left 3.2left 3.3line 4line 5", ); let right_id = testutils::write_file( store, path, "line 1line 2right 3.1line 4line 5", ); // The left side should come first. The diff should be use the smaller (right) // side, and the left side should be a snapshot. let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(left_id.clone()), Some(right_id.clone())], ); insta::assert_snapshot!( &materialize_conflict_string(store, path, &conflict), @r###" line 1 line 2 <<<<<<< +++++++ left 3.1 left 3.2 left 3.3 %%%%%%% -line 3 +right 3.1 >>>>>>> line 4 line 5 "### ); // Swap the positive terms in the conflict. The diff should still use the right // side, but now the right side should come first. let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(right_id.clone()), Some(left_id.clone())], ); insta::assert_snapshot!( &materialize_conflict_string(store, path, &conflict), @r###" line 1 line 2 <<<<<<< %%%%%%% -line 3 +right 3.1 +++++++ left 3.1 left 3.2 left 3.3 >>>>>>> line 4 line 5 "### );} #[test]fn test_materialize_conflict_multi_rebase_conflicts() { let test_repo = TestRepo::init(); let store = test_repo.repo.store(); // Create changes (a, b, c) on top of the base, and linearize them. let path = RepoPath::from_internal_string("file"); let base_id = testutils::write_file( store, path, "line 1line 2 baseline 3", ); let a_id = testutils::write_file( store, path, "line 1line 2 a.1line 2 a.2line 2 a.3line 3", ); let b_id = testutils::write_file( store, path, "line 1line 2 b.1line 2 b.2line 3", ); let c_id = testutils::write_file( store, path, "line 1line 2 c.1line 3", ); // The order of (a, b, c) should be preserved. For all cases, the "a" side // should be a snapshot. let conflict = Merge::from_removes_adds( vec![Some(base_id.clone()), Some(base_id.clone())], vec![Some(a_id.clone()), Some(b_id.clone()), Some(c_id.clone())], ); insta::assert_snapshot!( &materialize_conflict_string(store, path, &conflict), @r###" line 1 <<<<<<< +++++++ line 2 a.1 line 2 a.2 line 2 a.3 %%%%%%% -line 2 base +line 2 b.1 +line 2 b.2 %%%%%%% -line 2 base +line 2 c.1 >>>>>>> line 3 "### ); let conflict = Merge::from_removes_adds( vec![Some(base_id.clone()), Some(base_id.clone())], vec![Some(c_id.clone()), Some(b_id.clone()), Some(a_id.clone())], ); insta::assert_snapshot!( &materialize_conflict_string(store, path, &conflict), @r###" line 1 <<<<<<< %%%%%%% -line 2 base +line 2 c.1 %%%%%%% -line 2 base +line 2 b.1 +line 2 b.2 +++++++ line 2 a.1 line 2 a.2 line 2 a.3 >>>>>>> line 3 "### ); let conflict = Merge::from_removes_adds( vec![Some(base_id.clone()), Some(base_id.clone())], vec![Some(c_id.clone()), Some(a_id.clone()), Some(b_id.clone())], ); insta::assert_snapshot!( &materialize_conflict_string(store, path, &conflict), @r###" line 1 <<<<<<< %%%%%%% -line 2 base +line 2 c.1 +++++++ line 2 a.1 line 2 a.2 line 2 a.3 %%%%%%% -line 2 base +line 2 b.1 +line 2 b.2 >>>>>>> line 3 "### );} #[test]fn test_materialize_parse_roundtrip() { let test_repo = TestRepo::init(); let store = test_repo.repo.store(); let path = RepoPath::from_internal_string("file"); let base_id = testutils::write_file( store, path, "line 1line 2line 3line 4line 5", ); let left_id = testutils::write_file( store, path, "line 1 leftline 2 leftline 3line 4line 5 left", ); let right_id = testutils::write_file( store, path, "line 1 rightline 2line 3line 4 rightline 5 right", ); let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(left_id.clone()), Some(right_id.clone())], ); let materialized = materialize_conflict_string(store, path, &conflict); insta::assert_snapshot!( materialized, @r###" <<<<<<< +++++++ line 1 left line 2 left %%%%%%% -line 1 +line 1 right line 2 >>>>>>> line 3 <<<<<<< %%%%%%% line 4 -line 5 +line 5 left +++++++ line 4 right line 5 right >>>>>>> "### ); // The first add should always be from the left side insta::assert_debug_snapshot!( parse_conflict(materialized.as_bytes(), conflict.num_sides()), @r###" Some( [ Conflicted( [ "line 1 left\nline 2 left\n", "line 1\nline 2\n", "line 1 right\nline 2\n", ], ), Resolved( "line 3\n", ), Conflicted( [ "line 4\nline 5 left\n", "line 4\nline 5\n", "line 4 right\nline 5 right\n", ], ), ], ) "###);} #[test]fn test_materialize_conflict_modify_delete() { let test_repo = TestRepo::init(); let store = test_repo.repo.store(); let path = RepoPath::from_internal_string("file"); let base_id = testutils::write_file( store, path, "line 1line 2line 3line 4line 5", ); let modified_id = testutils::write_file( store, path, "line 1line 2modifiedline 4line 5", ); let deleted_id = testutils::write_file( store, path, "line 1line 2line 4line 5", ); // left modifies a line, right deletes the same line. let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(modified_id.clone()), Some(deleted_id.clone())], ); insta::assert_snapshot!(&materialize_conflict_string(store, path, &conflict), @r###" line 1 line 2 <<<<<<< +++++++ modified %%%%%%% -line 3 >>>>>>> line 4 line 5 "### ); // right modifies a line, left deletes the same line. let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(deleted_id.clone()), Some(modified_id.clone())], ); insta::assert_snapshot!(&materialize_conflict_string(store, path, &conflict), @r###" line 1 line 2 <<<<<<< %%%%%%% -line 3 +++++++ modified >>>>>>> line 4 line 5 "### ); // modify/delete conflict at the file level let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(modified_id.clone()), None], ); insta::assert_snapshot!(&materialize_conflict_string(store, path, &conflict), @r###" <<<<<<< %%%%%%% line 1 line 2 -line 3 +modified line 4 line 5 +++++++ >>>>>>> "### );} #[test]fn test_materialize_conflict_two_forward_diffs() { let test_repo = TestRepo::init(); let store = test_repo.repo.store(); // Create conflict A-B+B-C+D-E+C. This is designed to tempt the algorithm to // produce a negative snapshot at the end like this: // <<<< // ==== // A // %%%% // B // ++++ // D // %%%% // C // ---- // E // >>>> // TODO: Maybe we should never have negative snapshots let path = RepoPath::from_internal_string("file"); let a_id = testutils::write_file(store, path, "A\n"); let b_id = testutils::write_file(store, path, "B\n"); let c_id = testutils::write_file(store, path, "C\n"); let d_id = testutils::write_file(store, path, "D\n"); let e_id = testutils::write_file(store, path, "E\n"); let conflict = Merge::from_removes_adds( vec![Some(b_id.clone()), Some(c_id.clone()), Some(e_id.clone())], vec![ Some(a_id.clone()), Some(b_id.clone()), Some(d_id.clone()), Some(c_id.clone()), ], ); insta::assert_snapshot!( &materialize_conflict_string(store, path, &conflict), @r###" <<<<<<< +++++++ A %%%%%%% B +++++++ D %%%%%%% C ------- E >>>>>>> "### );} #[test]fn test_parse_conflict_resolved() { assert_eq!( parse_conflict( b"line 1line 2line 3line 4line 5", 2 ), None )} #[test]fn test_parse_conflict_simple() { insta::assert_debug_snapshot!( parse_conflict( b"line 1<<<<<<<%%%%%%%Lines 1–500 of 752. Load more
// Copyright 2021 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use jj_lib::backend::FileId; use jj_lib::conflicts::{ extract_as_single_hunk, materialize_merge_result, parse_conflict, update_from_content, }; use jj_lib::merge::Merge; use jj_lib::repo::Repo; use jj_lib::repo_path::RepoPath; use jj_lib::store::Store; use pollster::FutureExt; use testutils::TestRepo; #[test] fn test_materialize_conflict_basic() { let test_repo = TestRepo::init(); let store = test_repo.repo.store(); let path = RepoPath::from_internal_string("file"); let base_id = testutils::write_file( store, path, "line 1 line 2 line 3 line 4 line 5 ", ); let left_id = testutils::write_file( store, path, "line 1 line 2 left 3.1 left 3.2 left 3.3 line 4 line 5 ", ); let right_id = testutils::write_file( store, path, "line 1 line 2 right 3.1 line 4 line 5 ", ); // The left side should come first. The diff should be use the smaller (right) // side, and the left side should be a snapshot. let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(left_id.clone()), Some(right_id.clone())], ); insta::assert_snapshot!( &materialize_conflict_string(store, path, &conflict), @r###" line 1 line 2 <<<<<<< +++++++ left 3.1 left 3.2 left 3.3 %%%%%%% -line 3 +right 3.1 >>>>>>> line 4 line 5 "### ); // Swap the positive terms in the conflict. The diff should still use the right // side, but now the right side should come first. let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(right_id.clone()), Some(left_id.clone())], ); insta::assert_snapshot!( &materialize_conflict_string(store, path, &conflict), @r###" line 1 line 2 <<<<<<< %%%%%%% -line 3 +right 3.1 +++++++ left 3.1 left 3.2 left 3.3 >>>>>>> line 4 line 5 "### ); } #[test] fn test_materialize_conflict_multi_rebase_conflicts() { let test_repo = TestRepo::init(); let store = test_repo.repo.store(); // Create changes (a, b, c) on top of the base, and linearize them. let path = RepoPath::from_internal_string("file"); let base_id = testutils::write_file( store, path, "line 1 line 2 base line 3 ", ); let a_id = testutils::write_file( store, path, "line 1 line 2 a.1 line 2 a.2 line 2 a.3 line 3 ", ); let b_id = testutils::write_file( store, path, "line 1 line 2 b.1 line 2 b.2 line 3 ", ); let c_id = testutils::write_file( store, path, "line 1 line 2 c.1 line 3 ", ); // The order of (a, b, c) should be preserved. For all cases, the "a" side // should be a snapshot. let conflict = Merge::from_removes_adds( vec![Some(base_id.clone()), Some(base_id.clone())], vec![Some(a_id.clone()), Some(b_id.clone()), Some(c_id.clone())], ); insta::assert_snapshot!( &materialize_conflict_string(store, path, &conflict), @r###" line 1 <<<<<<< +++++++ line 2 a.1 line 2 a.2 line 2 a.3 %%%%%%% -line 2 base +line 2 b.1 +line 2 b.2 %%%%%%% -line 2 base +line 2 c.1 >>>>>>> line 3 "### ); let conflict = Merge::from_removes_adds( vec![Some(base_id.clone()), Some(base_id.clone())], vec![Some(c_id.clone()), Some(b_id.clone()), Some(a_id.clone())], ); insta::assert_snapshot!( &materialize_conflict_string(store, path, &conflict), @r###" line 1 <<<<<<< %%%%%%% -line 2 base +line 2 c.1 %%%%%%% -line 2 base +line 2 b.1 +line 2 b.2 +++++++ line 2 a.1 line 2 a.2 line 2 a.3 >>>>>>> line 3 "### ); let conflict = Merge::from_removes_adds( vec![Some(base_id.clone()), Some(base_id.clone())], vec![Some(c_id.clone()), Some(a_id.clone()), Some(b_id.clone())], ); insta::assert_snapshot!( &materialize_conflict_string(store, path, &conflict), @r###" line 1 <<<<<<< %%%%%%% -line 2 base +line 2 c.1 +++++++ line 2 a.1 line 2 a.2 line 2 a.3 %%%%%%% -line 2 base +line 2 b.1 +line 2 b.2 >>>>>>> line 3 "### ); } #[test] fn test_materialize_parse_roundtrip() { let test_repo = TestRepo::init(); let store = test_repo.repo.store(); let path = RepoPath::from_internal_string("file"); let base_id = testutils::write_file( store, path, "line 1 line 2 line 3 line 4 line 5 ", ); let left_id = testutils::write_file( store, path, "line 1 left line 2 left line 3 line 4 line 5 left ", ); let right_id = testutils::write_file( store, path, "line 1 right line 2 line 3 line 4 right line 5 right ", ); let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(left_id.clone()), Some(right_id.clone())], ); let materialized = materialize_conflict_string(store, path, &conflict); insta::assert_snapshot!( materialized, @r###" <<<<<<< +++++++ line 1 left line 2 left %%%%%%% -line 1 +line 1 right line 2 >>>>>>> line 3 <<<<<<< %%%%%%% line 4 -line 5 +line 5 left +++++++ line 4 right line 5 right >>>>>>> "### ); // The first add should always be from the left side insta::assert_debug_snapshot!( parse_conflict(materialized.as_bytes(), conflict.num_sides()), @r###" Some( [ Conflicted( [ "line 1 left\nline 2 left\n", "line 1\nline 2\n", "line 1 right\nline 2\n", ], ), Resolved( "line 3\n", ), Conflicted( [ "line 4\nline 5 left\n", "line 4\nline 5\n", "line 4 right\nline 5 right\n", ], ), ], ) "###); } #[test] fn test_materialize_conflict_modify_delete() { let test_repo = TestRepo::init(); let store = test_repo.repo.store(); let path = RepoPath::from_internal_string("file"); let base_id = testutils::write_file( store, path, "line 1 line 2 line 3 line 4 line 5 ", ); let modified_id = testutils::write_file( store, path, "line 1 line 2 modified line 4 line 5 ", ); let deleted_id = testutils::write_file( store, path, "line 1 line 2 line 4 line 5 ", ); // left modifies a line, right deletes the same line. let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(modified_id.clone()), Some(deleted_id.clone())], ); insta::assert_snapshot!(&materialize_conflict_string(store, path, &conflict), @r###" line 1 line 2 <<<<<<< +++++++ modified %%%%%%% -line 3 >>>>>>> line 4 line 5 "### ); // right modifies a line, left deletes the same line. let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(deleted_id.clone()), Some(modified_id.clone())], ); insta::assert_snapshot!(&materialize_conflict_string(store, path, &conflict), @r###" line 1 line 2 <<<<<<< %%%%%%% -line 3 +++++++ modified >>>>>>> line 4 line 5 "### ); // modify/delete conflict at the file level let conflict = Merge::from_removes_adds( vec![Some(base_id.clone())], vec![Some(modified_id.clone()), None], ); insta::assert_snapshot!(&materialize_conflict_string(store, path, &conflict), @r###" <<<<<<< %%%%%%% line 1 line 2 -line 3 +modified line 4 line 5 +++++++ >>>>>>> "### ); } #[test] fn test_materialize_conflict_two_forward_diffs() { let test_repo = TestRepo::init(); let store = test_repo.repo.store(); // Create conflict A-B+B-C+D-E+C. This is designed to tempt the algorithm to // produce a negative snapshot at the end like this: // <<<< // ==== // A // %%%% // B // ++++ // D // %%%% // C // ---- // E // >>>> // TODO: Maybe we should never have negative snapshots let path = RepoPath::from_internal_string("file"); let a_id = testutils::write_file(store, path, "A\n"); let b_id = testutils::write_file(store, path, "B\n"); let c_id = testutils::write_file(store, path, "C\n"); let d_id = testutils::write_file(store, path, "D\n"); let e_id = testutils::write_file(store, path, "E\n"); let conflict = Merge::from_removes_adds( vec![Some(b_id.clone()), Some(c_id.clone()), Some(e_id.clone())], vec![ Some(a_id.clone()), Some(b_id.clone()), Some(d_id.clone()), Some(c_id.clone()), ], ); insta::assert_snapshot!( &materialize_conflict_string(store, path, &conflict), @r###" <<<<<<< +++++++ A %%%%%%% B +++++++ D %%%%%%% C ------- E >>>>>>> "### ); } #[test] fn test_parse_conflict_resolved() { assert_eq!( parse_conflict( b"line 1 line 2 line 3 line 4 line 5 ", 2 ), None ) } #[test] fn test_parse_conflict_simple() { insta::assert_debug_snapshot!( parse_conflict( b"line 1 <<<<<<< %%%%%%%