// 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 std::slice; use assert_matches::assert_matches;use itertools::Itertools as _;use jj_lib::backend::ChangeId;use jj_lib::commit::Commit;use jj_lib::matchers::EverythingMatcher;use jj_lib::matchers::FilesMatcher;use jj_lib::merge::Merge;use jj_lib::merged_tree::MergedTree;use jj_lib::op_store::RefTarget;use jj_lib::op_store::RemoteRef;use jj_lib::op_store::RemoteRefState;use jj_lib::ref_name::RefName;use jj_lib::ref_name::RemoteName;use jj_lib::ref_name::RemoteRefSymbol;use jj_lib::ref_name::WorkspaceName;use jj_lib::ref_name::WorkspaceNameBuf;use jj_lib::repo::Repo as _;use jj_lib::revset::RevsetExpression;use jj_lib::rewrite::CommitRewriter;use jj_lib::rewrite::CommitWithSelection;use jj_lib::rewrite::EmptyBehavior;use jj_lib::rewrite::MoveCommitsTarget;use jj_lib::rewrite::RebaseOptions;use jj_lib::rewrite::RebasedCommit;use jj_lib::rewrite::RewriteRefsOptions;use jj_lib::rewrite::find_duplicate_divergent_commits;use jj_lib::rewrite::find_recursive_merge_commits;use jj_lib::rewrite::merge_commit_trees;use jj_lib::rewrite::merge_commit_trees_no_resolve;use jj_lib::rewrite::rebase_commit_with_options;use jj_lib::rewrite::restore_tree;use maplit::hashmap;use maplit::hashset;use pollster::FutureExt as _;use test_case::test_case;use testutils::CommitBuilderExt as _;use testutils::TestRepo;use testutils::TestResult;use testutils::assert_abandoned_with_parent;use testutils::assert_rebased_onto;use testutils::assert_tree_eq;use testutils::create_random_commit;use testutils::create_tree;use testutils::create_tree_with;use testutils::rebase_descendants_with_options_return_map;use testutils::repo_path;use testutils::write_random_commit;use testutils::write_random_commit_with_parents; fn remote_symbol<'a, N, M>(name: &'a N, remote: &'a M) -> RemoteRefSymbol<'a>where N: AsRef<RefName> + ?Sized, M: AsRef<RemoteName> + ?Sized,{ RemoteRefSymbol { name: name.as_ref(), remote: remote.as_ref(), }} /// Based on https://lore.kernel.org/git/Pine.LNX.4.44.0504271254120.4678-100000@wax.eds.org//// (found in t/t6401-merge-criss-cross.sh in the git.git repo).#[test]fn test_merge_criss_cross() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let path = repo_path("file"); let tree_a = create_tree(repo, &[(path, "1\n2\n3\n4\n5\n6\n7\n8\n9\n")]); let tree_b = create_tree(repo, &[(path, "1\n2\n3\n4\n5\n6\n7\n8B\n9\n")]); let tree_c = create_tree(repo, &[(path, "1\n2\n3C\n4\n5\n6\n7\n8\n9\n")]); let tree_d = create_tree(repo, &[(path, "1\n2\n3C\n4\n5\n6\n7\n8D\n9\n")]); let tree_e = create_tree(repo, &[(path, "1\n2\n3E\n4\n5\n6\n7\n8B\n9\n")]); let tree_expected = create_tree(repo, &[(path, "1\n2\n3E\n4\n5\n6\n7\n8D\n9\n")]); let mut tx = repo.start_transaction(); let mut make_commit = |description, parents, tree| { tx.repo_mut() .new_commit(parents, tree) .set_description(description) .write_unwrap() }; let commit_a = make_commit( "A", vec![repo.store().root_commit_id().clone()], tree_a.clone(), ); let commit_b = make_commit("B", vec![commit_a.id().clone()], tree_b.clone()); let commit_c = make_commit("C", vec![commit_a.id().clone()], tree_c.clone()); let commit_d = make_commit( "D", vec![commit_b.id().clone(), commit_c.id().clone()], tree_d.clone(), ); let commit_e = make_commit( "E", vec![commit_b.id().clone(), commit_c.id().clone()], tree_e.clone(), ); let merged = merge_commit_trees(tx.repo_mut(), &[commit_d.clone(), commit_e.clone()]).block_on()?; assert_tree_eq!(merged, tree_expected); let tree_unresolved_expected = MergedTree::merge_no_resolve(Merge::from_vec(vec![ (tree_d, commit_d.conflict_label()), (tree_b, commit_b.conflict_label()), (tree_a, commit_a.conflict_label()), (tree_c, commit_c.conflict_label()), (tree_e, commit_e.conflict_label()), ])); let unresolved_merged = merge_commit_trees_no_resolve(tx.repo_mut(), &[commit_d, commit_e]).block_on()?; assert_tree_eq!(unresolved_merged, tree_unresolved_expected); assert_tree_eq!(unresolved_merged.resolve().block_on()?, tree_expected); Ok(())} #[test]fn test_find_recursive_merge_commits() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let commit_a = write_random_commit(tx.repo_mut()); let commit_b = write_random_commit_with_parents(tx.repo_mut(), &[&commit_a]); let commit_c = write_random_commit_with_parents(tx.repo_mut(), &[&commit_a]); let commit_d = write_random_commit_with_parents(tx.repo_mut(), &[&commit_b, &commit_c]); let commit_e = write_random_commit_with_parents(tx.repo_mut(), &[&commit_b, &commit_c]); let commit_id_merge = find_recursive_merge_commits( tx.repo().store(), tx.repo().index(), vec![commit_d.id().clone(), commit_e.id().clone()], )?; assert_eq!( commit_id_merge, Merge::from_vec(vec![ commit_d.id().clone(), commit_b.id().clone(), commit_a.id().clone(), commit_c.id().clone(), commit_e.id().clone(), ]) ); Ok(())} #[test]fn test_restore_tree() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let path1 = repo_path("file1"); let path2 = repo_path("dir1/file2"); let path3 = repo_path("dir1/file3"); let path4 = repo_path("dir2/file4"); let left = create_tree(repo, &[(path2, "left"), (path3, "left"), (path4, "left")]); let right = create_tree( repo, &[(path1, "right"), (path2, "right"), (path3, "right")], ); // Restore everything using EverythingMatcher let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &EverythingMatcher, ) .block_on()?; assert_tree_eq!(restored, left); // Restore everything using FilesMatcher let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &FilesMatcher::new([&path1, &path2, &path3, &path4]), ) .block_on()?; assert_tree_eq!(restored, left); // Restore some files let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &FilesMatcher::new([path1, path2]), ) .block_on()?; let expected = create_tree(repo, &[(path2, "left"), (path3, "right")]); assert_tree_eq!(restored, expected); Ok(())} #[test]fn test_restore_tree_with_conflicts() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let path1 = repo_path("file1"); let path2 = repo_path("dir1/file2"); let path3 = repo_path("dir1/file3"); // Create two 2-sided conflicts. let left_side1 = create_tree(repo, &[(path1, "left side 1"), (path2, "left side 1")]); let left_base1 = create_tree(repo, &[(path1, "left base 1"), (path2, "left base 1")]); let left_side2 = create_tree(repo, &[(path1, "left side 2"), (path2, "left side 2")]); let right_side1 = create_tree(repo, &[(path1, "right side 1"), (path2, "right side 1")]); let right_base1 = create_tree(repo, &[(path1, "right base"), (path2, "right base")]); let right_side2 = create_tree(repo, &[(path1, "right side 2"), (path2, "right side 2")]); let left = MergedTree::merge(Merge::from_vec(vec![ (left_side1, "left side 1".into()), (left_base1, "left base 1".into()), (left_side2, "left side 2".into()), ])) .block_on()?; let right = MergedTree::merge(Merge::from_vec(vec![ (right_side1.clone(), "right side 1".into()), (right_base1.clone(), "right base 1".into()), (right_side2.clone(), "right side 2".into()), ])) .block_on()?; // Restore everything using EverythingMatcher let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &EverythingMatcher, ) .block_on()?; assert_tree_eq!(restored, left); // Restore a single conflicted file let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &FilesMatcher::new([path1]), ) .block_on()?; // After simplifying, the result should have path 1 from the left side and path // 2 from the right side. assert_eq!( restored.path_value(path1).block_on()?.simplify(), left.path_value(path1).block_on()?.simplify() ); assert_eq!( restored.path_value(path2).block_on()?.simplify(), right.path_value(path2).block_on()?.simplify() ); let expected_base_side1 = create_tree(repo, &[(path1, "right side 1")]); let expected_base_base1 = create_tree(repo, &[(path1, "right base")]); let expected_base_side2 = create_tree(repo, &[(path1, "right side 2")]); let expected_left_side1 = create_tree(repo, &[(path1, "left side 1")]); let expected_left_base1 = create_tree(repo, &[(path1, "left base 1")]); let expected_left_side2 = create_tree(repo, &[(path1, "left side 2")]); let expected = MergedTree::merge_no_resolve(Merge::from_vec(vec![ (right_side1.clone(), "right side 1".into()), (right_base1.clone(), "right base 1".into()), (right_side2.clone(), "right side 2".into()), ( expected_base_side2.clone(), "base files for restore (from right side 2)".into(), ), ( expected_base_base1.clone(), "base files for restore (from right base 1)".into(), ), ( expected_base_side1.clone(), "base files for restore (from right side 1)".into(), ), (expected_left_side1, "left side 1".into()), (expected_left_base1, "left base 1".into()), (expected_left_side2, "left side 2".into()), ])); assert_tree_eq!(restored, expected); // Create a 3-sided conflict and a 2-sided conflict. let left_side1 = create_tree( repo, &[ (path1, "left side 1"), (path2, "left side 1"), (path3, "left"), ], ); let left_base1 = create_tree( repo, &[ (path1, "left base"), (path2, "left base 1"), (path3, "left"), ], ); let left_side2 = create_tree( repo, &[ (path1, "left side 2"), (path2, "left side 2"), (path3, "left"), ], ); let left_base2 = create_tree( repo, &[ (path1, "left base"), (path2, "left base 2"), (path3, "left"), ], ); let left_side3 = create_tree( repo, &[ (path1, "left base"), (path2, "left side 3"), (path3, "left"), ], ); let right_side1 = create_tree( repo, &[ (path1, "right side 1"), (path2, "resolved"), (path3, "right"), ], ); let right_base1 = create_tree( repo, &[(path1, "right base"), (path2, "resolved"), (path3, "right")], ); let right_side2 = create_tree( repo, &[ (path1, "right side 2"), (path2, "resolved"), (path3, "right"), ], ); let left = MergedTree::merge(Merge::from_vec(vec![ (left_side1, "left side 1".into()), (left_base1, "left base 1".into()), (left_side2, "left side 2".into()), (left_base2, "left base 2".into()), (left_side3, "left side 3".into()), ])) .block_on()?; let right = MergedTree::merge(Merge::from_vec(vec![ (right_side1.clone(), "right side 1".into()), (right_base1.clone(), "right base 1".into()), (right_side2.clone(), "right side 2".into()), ])) .block_on()?; // Restore everything using EverythingMatcher let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &EverythingMatcher, ) .block_on()?; assert_tree_eq!(restored, left); // Restore a single conflicted file let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &FilesMatcher::new([path2]), ) .block_on()?; // After simplifying, the result should have paths 1 and 3 from the right side // and path 2 from the left side. assert_eq!( restored.path_value(path1).block_on()?.simplify(), right.path_value(path1).block_on()?.simplify() ); assert_eq!( restored.path_value(path2).block_on()?.simplify(), left.path_value(path2).block_on()?.simplify() ); assert_eq!( restored.path_value(path3).block_on()?.simplify(), right.path_value(path3).block_on()?.simplify() ); // path3 is included in all trees due to `MergedTree::resolve`. let expected_base = create_tree(repo, &[(path2, "resolved"), (path3, "right")]); let expected_left_side1 = create_tree(repo, &[(path2, "left side 1"), (path3, "right")]); let expected_left_base1 = create_tree(repo, &[(path2, "left base 1"), (path3, "right")]); let expected_left_side2 = create_tree(repo, &[(path2, "left side 2"), (path3, "right")]); let expected_left_base2 = create_tree(repo, &[(path2, "left base 2"), (path3, "right")]); let expected_left_side3 = create_tree(repo, &[(path2, "left side 3"), (path3, "right")]); let expected = MergedTree::merge_no_resolve(Merge::from_vec(vec![ (right_side1.clone(), "right side 1".into()), (right_base1.clone(), "right base 1".into()), (right_side2.clone(), "right side 2".into()), ( expected_base, "base files for restore (from right side)".into(), ), (expected_left_side1, "left side 1".into()), (expected_left_base1, "left base 1".into()), (expected_left_side2, "left side 2".into()), (expected_left_base2, "left base 2".into()), (expected_left_side3, "left side 3".into()), ])); assert_tree_eq!(restored, expected); // Restore a single resolved file let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &FilesMatcher::new([path3]), ) .block_on()?; // After simplifying, the result should have paths 1 and 2 from the right side // and path 3 from the left side. assert_eq!( restored.path_value(path1).block_on()?.simplify(), right.path_value(path1).block_on()?.simplify() ); assert_eq!( restored.path_value(path2).block_on()?.simplify(), right.path_value(path2).block_on()?.simplify() ); assert_eq!( restored.path_value(path3).block_on()?.simplify(), left.path_value(path3).block_on()?.simplify() ); // path3 is updated in the existing conflict terms due to `MergedTree::resolve`. // If we implement https://github.com/jj-vcs/jj/issues/4152, the conflict would // instead have an extra side showing the diff from the restore. let expected_side1 = create_tree( repo, &[ (path1, "right side 1"), (path2, "resolved"), (path3, "left"), ], ); let expected_base1 = create_tree( repo, &[(path1, "right base"), (path2, "resolved"), (path3, "left")], ); let expected_side2 = create_tree( repo, &[ (path1, "right side 2"), (path2, "resolved"), (path3, "left"), ], ); let expected = MergedTree::merge_no_resolve(Merge::from_vec(vec![ (expected_side1.clone(), "right side 1".into()), (expected_base1.clone(), "right base 1".into()), (expected_side2.clone(), "right side 2".into()), ])); assert_tree_eq!(restored, expected); Ok(())} #[test]Lines 1–500 of 2443. 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 std::slice; use assert_matches::assert_matches; use itertools::Itertools as _; use jj_lib::backend::ChangeId; use jj_lib::commit::Commit; use jj_lib::matchers::EverythingMatcher; use jj_lib::matchers::FilesMatcher; use jj_lib::merge::Merge; use jj_lib::merged_tree::MergedTree; use jj_lib::op_store::RefTarget; use jj_lib::op_store::RemoteRef; use jj_lib::op_store::RemoteRefState; use jj_lib::ref_name::RefName; use jj_lib::ref_name::RemoteName; use jj_lib::ref_name::RemoteRefSymbol; use jj_lib::ref_name::WorkspaceName; use jj_lib::ref_name::WorkspaceNameBuf; use jj_lib::repo::Repo as _; use jj_lib::revset::RevsetExpression; use jj_lib::rewrite::CommitRewriter; use jj_lib::rewrite::CommitWithSelection; use jj_lib::rewrite::EmptyBehavior; use jj_lib::rewrite::MoveCommitsTarget; use jj_lib::rewrite::RebaseOptions; use jj_lib::rewrite::RebasedCommit; use jj_lib::rewrite::RewriteRefsOptions; use jj_lib::rewrite::find_duplicate_divergent_commits; use jj_lib::rewrite::find_recursive_merge_commits; use jj_lib::rewrite::merge_commit_trees; use jj_lib::rewrite::merge_commit_trees_no_resolve; use jj_lib::rewrite::rebase_commit_with_options; use jj_lib::rewrite::restore_tree; use maplit::hashmap; use maplit::hashset; use pollster::FutureExt as _; use test_case::test_case; use testutils::CommitBuilderExt as _; use testutils::TestRepo; use testutils::TestResult; use testutils::assert_abandoned_with_parent; use testutils::assert_rebased_onto; use testutils::assert_tree_eq; use testutils::create_random_commit; use testutils::create_tree; use testutils::create_tree_with; use testutils::rebase_descendants_with_options_return_map; use testutils::repo_path; use testutils::write_random_commit; use testutils::write_random_commit_with_parents; fn remote_symbol<'a, N, M>(name: &'a N, remote: &'a M) -> RemoteRefSymbol<'a> where N: AsRef<RefName> + ?Sized, M: AsRef<RemoteName> + ?Sized, { RemoteRefSymbol { name: name.as_ref(), remote: remote.as_ref(), } } /// Based on https://lore.kernel.org/git/Pine.LNX.4.44.0504271254120.4678-100000@wax.eds.org/ /// (found in t/t6401-merge-criss-cross.sh in the git.git repo). #[test] fn test_merge_criss_cross() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let path = repo_path("file"); let tree_a = create_tree(repo, &[(path, "1\n2\n3\n4\n5\n6\n7\n8\n9\n")]); let tree_b = create_tree(repo, &[(path, "1\n2\n3\n4\n5\n6\n7\n8B\n9\n")]); let tree_c = create_tree(repo, &[(path, "1\n2\n3C\n4\n5\n6\n7\n8\n9\n")]); let tree_d = create_tree(repo, &[(path, "1\n2\n3C\n4\n5\n6\n7\n8D\n9\n")]); let tree_e = create_tree(repo, &[(path, "1\n2\n3E\n4\n5\n6\n7\n8B\n9\n")]); let tree_expected = create_tree(repo, &[(path, "1\n2\n3E\n4\n5\n6\n7\n8D\n9\n")]); let mut tx = repo.start_transaction(); let mut make_commit = |description, parents, tree| { tx.repo_mut() .new_commit(parents, tree) .set_description(description) .write_unwrap() }; let commit_a = make_commit( "A", vec![repo.store().root_commit_id().clone()], tree_a.clone(), ); let commit_b = make_commit("B", vec![commit_a.id().clone()], tree_b.clone()); let commit_c = make_commit("C", vec![commit_a.id().clone()], tree_c.clone()); let commit_d = make_commit( "D", vec![commit_b.id().clone(), commit_c.id().clone()], tree_d.clone(), ); let commit_e = make_commit( "E", vec![commit_b.id().clone(), commit_c.id().clone()], tree_e.clone(), ); let merged = merge_commit_trees(tx.repo_mut(), &[commit_d.clone(), commit_e.clone()]).block_on()?; assert_tree_eq!(merged, tree_expected); let tree_unresolved_expected = MergedTree::merge_no_resolve(Merge::from_vec(vec![ (tree_d, commit_d.conflict_label()), (tree_b, commit_b.conflict_label()), (tree_a, commit_a.conflict_label()), (tree_c, commit_c.conflict_label()), (tree_e, commit_e.conflict_label()), ])); let unresolved_merged = merge_commit_trees_no_resolve(tx.repo_mut(), &[commit_d, commit_e]).block_on()?; assert_tree_eq!(unresolved_merged, tree_unresolved_expected); assert_tree_eq!(unresolved_merged.resolve().block_on()?, tree_expected); Ok(()) } #[test] fn test_find_recursive_merge_commits() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let commit_a = write_random_commit(tx.repo_mut()); let commit_b = write_random_commit_with_parents(tx.repo_mut(), &[&commit_a]); let commit_c = write_random_commit_with_parents(tx.repo_mut(), &[&commit_a]); let commit_d = write_random_commit_with_parents(tx.repo_mut(), &[&commit_b, &commit_c]); let commit_e = write_random_commit_with_parents(tx.repo_mut(), &[&commit_b, &commit_c]); let commit_id_merge = find_recursive_merge_commits( tx.repo().store(), tx.repo().index(), vec![commit_d.id().clone(), commit_e.id().clone()], )?; assert_eq!( commit_id_merge, Merge::from_vec(vec![ commit_d.id().clone(), commit_b.id().clone(), commit_a.id().clone(), commit_c.id().clone(), commit_e.id().clone(), ]) ); Ok(()) } #[test] fn test_restore_tree() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let path1 = repo_path("file1"); let path2 = repo_path("dir1/file2"); let path3 = repo_path("dir1/file3"); let path4 = repo_path("dir2/file4"); let left = create_tree(repo, &[(path2, "left"), (path3, "left"), (path4, "left")]); let right = create_tree( repo, &[(path1, "right"), (path2, "right"), (path3, "right")], ); // Restore everything using EverythingMatcher let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &EverythingMatcher, ) .block_on()?; assert_tree_eq!(restored, left); // Restore everything using FilesMatcher let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &FilesMatcher::new([&path1, &path2, &path3, &path4]), ) .block_on()?; assert_tree_eq!(restored, left); // Restore some files let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &FilesMatcher::new([path1, path2]), ) .block_on()?; let expected = create_tree(repo, &[(path2, "left"), (path3, "right")]); assert_tree_eq!(restored, expected); Ok(()) } #[test] fn test_restore_tree_with_conflicts() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let path1 = repo_path("file1"); let path2 = repo_path("dir1/file2"); let path3 = repo_path("dir1/file3"); // Create two 2-sided conflicts. let left_side1 = create_tree(repo, &[(path1, "left side 1"), (path2, "left side 1")]); let left_base1 = create_tree(repo, &[(path1, "left base 1"), (path2, "left base 1")]); let left_side2 = create_tree(repo, &[(path1, "left side 2"), (path2, "left side 2")]); let right_side1 = create_tree(repo, &[(path1, "right side 1"), (path2, "right side 1")]); let right_base1 = create_tree(repo, &[(path1, "right base"), (path2, "right base")]); let right_side2 = create_tree(repo, &[(path1, "right side 2"), (path2, "right side 2")]); let left = MergedTree::merge(Merge::from_vec(vec![ (left_side1, "left side 1".into()), (left_base1, "left base 1".into()), (left_side2, "left side 2".into()), ])) .block_on()?; let right = MergedTree::merge(Merge::from_vec(vec![ (right_side1.clone(), "right side 1".into()), (right_base1.clone(), "right base 1".into()), (right_side2.clone(), "right side 2".into()), ])) .block_on()?; // Restore everything using EverythingMatcher let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &EverythingMatcher, ) .block_on()?; assert_tree_eq!(restored, left); // Restore a single conflicted file let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &FilesMatcher::new([path1]), ) .block_on()?; // After simplifying, the result should have path 1 from the left side and path // 2 from the right side. assert_eq!( restored.path_value(path1).block_on()?.simplify(), left.path_value(path1).block_on()?.simplify() ); assert_eq!( restored.path_value(path2).block_on()?.simplify(), right.path_value(path2).block_on()?.simplify() ); let expected_base_side1 = create_tree(repo, &[(path1, "right side 1")]); let expected_base_base1 = create_tree(repo, &[(path1, "right base")]); let expected_base_side2 = create_tree(repo, &[(path1, "right side 2")]); let expected_left_side1 = create_tree(repo, &[(path1, "left side 1")]); let expected_left_base1 = create_tree(repo, &[(path1, "left base 1")]); let expected_left_side2 = create_tree(repo, &[(path1, "left side 2")]); let expected = MergedTree::merge_no_resolve(Merge::from_vec(vec![ (right_side1.clone(), "right side 1".into()), (right_base1.clone(), "right base 1".into()), (right_side2.clone(), "right side 2".into()), ( expected_base_side2.clone(), "base files for restore (from right side 2)".into(), ), ( expected_base_base1.clone(), "base files for restore (from right base 1)".into(), ), ( expected_base_side1.clone(), "base files for restore (from right side 1)".into(), ), (expected_left_side1, "left side 1".into()), (expected_left_base1, "left base 1".into()), (expected_left_side2, "left side 2".into()), ])); assert_tree_eq!(restored, expected); // Create a 3-sided conflict and a 2-sided conflict. let left_side1 = create_tree( repo, &[ (path1, "left side 1"), (path2, "left side 1"), (path3, "left"), ], ); let left_base1 = create_tree( repo, &[ (path1, "left base"), (path2, "left base 1"), (path3, "left"), ], ); let left_side2 = create_tree( repo, &[ (path1, "left side 2"), (path2, "left side 2"), (path3, "left"), ], ); let left_base2 = create_tree( repo, &[ (path1, "left base"), (path2, "left base 2"), (path3, "left"), ], ); let left_side3 = create_tree( repo, &[ (path1, "left base"), (path2, "left side 3"), (path3, "left"), ], ); let right_side1 = create_tree( repo, &[ (path1, "right side 1"), (path2, "resolved"), (path3, "right"), ], ); let right_base1 = create_tree( repo, &[(path1, "right base"), (path2, "resolved"), (path3, "right")], ); let right_side2 = create_tree( repo, &[ (path1, "right side 2"), (path2, "resolved"), (path3, "right"), ], ); let left = MergedTree::merge(Merge::from_vec(vec![ (left_side1, "left side 1".into()), (left_base1, "left base 1".into()), (left_side2, "left side 2".into()), (left_base2, "left base 2".into()), (left_side3, "left side 3".into()), ])) .block_on()?; let right = MergedTree::merge(Merge::from_vec(vec![ (right_side1.clone(), "right side 1".into()), (right_base1.clone(), "right base 1".into()), (right_side2.clone(), "right side 2".into()), ])) .block_on()?; // Restore everything using EverythingMatcher let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &EverythingMatcher, ) .block_on()?; assert_tree_eq!(restored, left); // Restore a single conflicted file let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &FilesMatcher::new([path2]), ) .block_on()?; // After simplifying, the result should have paths 1 and 3 from the right side // and path 2 from the left side. assert_eq!( restored.path_value(path1).block_on()?.simplify(), right.path_value(path1).block_on()?.simplify() ); assert_eq!( restored.path_value(path2).block_on()?.simplify(), left.path_value(path2).block_on()?.simplify() ); assert_eq!( restored.path_value(path3).block_on()?.simplify(), right.path_value(path3).block_on()?.simplify() ); // path3 is included in all trees due to `MergedTree::resolve`. let expected_base = create_tree(repo, &[(path2, "resolved"), (path3, "right")]); let expected_left_side1 = create_tree(repo, &[(path2, "left side 1"), (path3, "right")]); let expected_left_base1 = create_tree(repo, &[(path2, "left base 1"), (path3, "right")]); let expected_left_side2 = create_tree(repo, &[(path2, "left side 2"), (path3, "right")]); let expected_left_base2 = create_tree(repo, &[(path2, "left base 2"), (path3, "right")]); let expected_left_side3 = create_tree(repo, &[(path2, "left side 3"), (path3, "right")]); let expected = MergedTree::merge_no_resolve(Merge::from_vec(vec![ (right_side1.clone(), "right side 1".into()), (right_base1.clone(), "right base 1".into()), (right_side2.clone(), "right side 2".into()), ( expected_base, "base files for restore (from right side)".into(), ), (expected_left_side1, "left side 1".into()), (expected_left_base1, "left base 1".into()), (expected_left_side2, "left side 2".into()), (expected_left_base2, "left base 2".into()), (expected_left_side3, "left side 3".into()), ])); assert_tree_eq!(restored, expected); // Restore a single resolved file let restored = restore_tree( &left, &right, "left side".into(), "right side".into(), &FilesMatcher::new([path3]), ) .block_on()?; // After simplifying, the result should have paths 1 and 2 from the right side // and path 3 from the left side. assert_eq!( restored.path_value(path1).block_on()?.simplify(), right.path_value(path1).block_on()?.simplify() ); assert_eq!( restored.path_value(path2).block_on()?.simplify(), right.path_value(path2).block_on()?.simplify() ); assert_eq!( restored.path_value(path3).block_on()?.simplify(), left.path_value(path3).block_on()?.simplify() ); // path3 is updated in the existing conflict terms due to `MergedTree::resolve`. // If we implement https://github.com/jj-vcs/jj/issues/4152, the conflict would // instead have an extra side showing the diff from the restore. let expected_side1 = create_tree( repo, &[ (path1, "right side 1"), (path2, "resolved"), (path3, "left"), ], ); let expected_base1 = create_tree( repo, &[(path1, "right base"), (path2, "resolved"), (path3, "left")], ); let expected_side2 = create_tree( repo, &[ (path1, "right side 2"), (path2, "resolved"), (path3, "left"), ], ); let expected = MergedTree::merge_no_resolve(Merge::from_vec(vec![ (expected_side1.clone(), "right side 1".into()), (expected_base1.clone(), "right base 1".into()), (expected_side2.clone(), "right side 2".into()), ])); assert_tree_eq!(restored, expected); Ok(()) } #[test]