// 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 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::rewrite::find_duplicate_divergent_commits;use jj_lib::rewrite::rebase_commit_with_options;use jj_lib::rewrite::restore_tree;use jj_lib::rewrite::CommitRewriter;use jj_lib::rewrite::CommitWithSelection;use jj_lib::rewrite::EmptyBehaviour;use jj_lib::rewrite::MoveCommitsTarget;use jj_lib::rewrite::RebaseOptions;use jj_lib::rewrite::RewriteRefsOptions;use maplit::hashmap;use maplit::hashset;use pollster::FutureExt as _;use test_case::test_case;use testutils::assert_abandoned_with_parent;use testutils::assert_rebased_onto;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::CommitGraphBuilder;use testutils::TestRepo; 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(), }} #[test]fn test_restore_tree() { 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, &EverythingMatcher) .block_on() .unwrap(); assert_eq!(restored, left.id()); // Restore everything using FilesMatcher let restored = restore_tree( &left, &right, &FilesMatcher::new([&path1, &path2, &path3, &path4]), ) .block_on() .unwrap(); assert_eq!(restored, left.id()); // Restore some files let restored = restore_tree(&left, &right, &FilesMatcher::new([path1, path2])) .block_on() .unwrap(); let expected = create_tree(repo, &[(path2, "left"), (path3, "right")]); assert_eq!(restored, expected.id());} #[test]fn test_rebase_descendants_sideways() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B was replaced by commit F. Commits C-E should be rebased. // // F // | D // | C E // | |/ // | B // |/ // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_c]); let commit_e = graph_builder.commit_with_parents(&[&commit_b]); let commit_f = graph_builder.commit_with_parents(&[&commit_a]); tx.repo_mut() .set_rewritten_commit(commit_b.id().clone(), commit_f.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); assert_eq!(rebase_map.len(), 3); let new_commit_c = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_c, &[commit_f.id()]); let new_commit_d = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_d, &[new_commit_c.id()]); let new_commit_e = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_e, &[commit_f.id()]); assert_eq!( *tx.repo().view().heads(), hashset! { new_commit_d.id().clone(), new_commit_e.id().clone() } );} #[test]fn test_rebase_descendants_forward() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B was replaced by commit F. Commits C and E should be rebased onto F. // Commit D does not get rebased because it's an ancestor of the // destination. Commit G does not get replaced because it's already in // place. // TODO: The above is not what actually happens! The test below shows what // actually happens: D and F also get rebased onto F, so we end up with // duplicates. Consider if it's worth supporting the case above better or if // that decision belongs with the caller (as we currently force it to do by // not supporting it in DescendantRebaser). // // G // F E // |/ // D C // |/ // B // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_b]); let commit_e = graph_builder.commit_with_parents(&[&commit_d]); let commit_f = graph_builder.commit_with_parents(&[&commit_d]); let commit_g = graph_builder.commit_with_parents(&[&commit_f]); tx.repo_mut() .set_rewritten_commit(commit_b.id().clone(), commit_f.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_d = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_d, &[(commit_f.id())]); let new_commit_f = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_f, &[new_commit_d.id()]); let new_commit_c = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_c, &[new_commit_f.id()]); let new_commit_e = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_e, &[new_commit_d.id()]); let new_commit_g = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_g, &[new_commit_f.id()]); assert_eq!(rebase_map.len(), 5); assert_eq!( *tx.repo().view().heads(), hashset! { new_commit_c.id().clone(), new_commit_e.id().clone(), new_commit_g.id().clone(), } );} #[test]fn test_rebase_descendants_reorder() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit E was replaced by commit D, and commit C was replaced by commit F // (attempting to to reorder C and E), and commit G was replaced by commit // H. // // I // G H // E F // C D // |/ // B // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_b]); let commit_e = graph_builder.commit_with_parents(&[&commit_c]); let commit_f = graph_builder.commit_with_parents(&[&commit_d]); let commit_g = graph_builder.commit_with_parents(&[&commit_e]); let commit_h = graph_builder.commit_with_parents(&[&commit_f]); let commit_i = graph_builder.commit_with_parents(&[&commit_g]); tx.repo_mut() .set_rewritten_commit(commit_e.id().clone(), commit_d.id().clone()); tx.repo_mut() .set_rewritten_commit(commit_c.id().clone(), commit_f.id().clone()); tx.repo_mut() .set_rewritten_commit(commit_g.id().clone(), commit_h.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_i = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_i, &[commit_h.id()]); assert_eq!(rebase_map.len(), 1); assert_eq!( *tx.repo().view().heads(), hashset! { new_commit_i.id().clone(), } );} #[test]fn test_rebase_descendants_backward() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit C was replaced by commit B. Commit D should be rebased. // // D // C // B // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_c]); tx.repo_mut() .set_rewritten_commit(commit_c.id().clone(), commit_b.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_d = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_d, &[commit_b.id()]); assert_eq!(rebase_map.len(), 1); assert_eq!( *tx.repo().view().heads(), hashset! {new_commit_d.id().clone()} );} #[test]fn test_rebase_descendants_chain_becomes_branchy() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B was replaced by commit E and commit C was replaced by commit F. // Commit F should get rebased onto E, and commit D should get rebased onto // the rebased F. // // D // C F // |/ // B E // |/ // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_c]); let commit_e = graph_builder.commit_with_parents(&[&commit_a]); let commit_f = graph_builder.commit_with_parents(&[&commit_b]); tx.repo_mut() .set_rewritten_commit(commit_b.id().clone(), commit_e.id().clone()); tx.repo_mut() .set_rewritten_commit(commit_c.id().clone(), commit_f.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_f = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_f, &[commit_e.id()]); let new_commit_d = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_d, &[new_commit_f.id()]); assert_eq!(rebase_map.len(), 2); assert_eq!( *tx.repo().view().heads(), hashset! { new_commit_d.id().clone(), } );} #[test]fn test_rebase_descendants_internal_merge() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B was replaced by commit F. Commits C-E should be rebased. // // F // | E // | |\ // | C D // | |/ // | B // |/ // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_b]); let commit_e = graph_builder.commit_with_parents(&[&commit_c, &commit_d]); let commit_f = graph_builder.commit_with_parents(&[&commit_a]); tx.repo_mut() .set_rewritten_commit(commit_b.id().clone(), commit_f.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_c = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_c, &[commit_f.id()]); let new_commit_d = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_d, &[commit_f.id()]); let new_commit_e = assert_rebased_onto( tx.repo_mut(), &rebase_map, &commit_e, &[new_commit_c.id(), new_commit_d.id()], ); assert_eq!(rebase_map.len(), 3); assert_eq!( *tx.repo().view().heads(), hashset! { new_commit_e.id().clone() } );} #[test]fn test_rebase_descendants_external_merge() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit C was replaced by commit F. Commits E should be rebased. The rebased // commit E should have F as first parent and commit D as second parent. // // F // | E // | |\ // | C D // | |/ // | B // |/ // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_b]); let commit_e = graph_builder.commit_with_parents(&[&commit_c, &commit_d]); let commit_f = graph_builder.commit_with_parents(&[&commit_a]); tx.repo_mut() .set_rewritten_commit(commit_c.id().clone(), commit_f.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_e = assert_rebased_onto( tx.repo_mut(), &rebase_map, &commit_e, &[commit_f.id(), commit_d.id()], ); assert_eq!(rebase_map.len(), 1); assert_eq!( *tx.repo().view().heads(), hashset! {new_commit_e.id().clone()} );} #[test]fn test_rebase_descendants_abandon() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B and commit E were abandoned. Commit C and commit D should get // rebased onto commit A. Commit F should get rebased onto the new commit D. // // F // E // D C // |/ // B // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_b]); let commit_e = graph_builder.commit_with_parents(&[&commit_d]); let commit_f = graph_builder.commit_with_parents(&[&commit_e]); tx.repo_mut().record_abandoned_commit(&commit_b); tx.repo_mut().record_abandoned_commit(&commit_e); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_c = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_c, &[commit_a.id()]); let new_commit_d = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_d, &[commit_a.id()]); let new_commit_f = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_f, &[new_commit_d.id()]); assert_eq!(rebase_map.len(), 3); assert_eq!( *tx.repo().view().heads(), hashset! { new_commit_c.id().clone(), new_commit_f.id().clone() } );} #[test]fn test_rebase_descendants_abandon_no_descendants() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B and C were abandoned. Commit A should become a head. // // C // B // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); tx.repo_mut().record_abandoned_commit(&commit_b); tx.repo_mut().record_abandoned_commit(&commit_c); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); assert_eq!(rebase_map.len(), 0); assert_eq!( *tx.repo().view().heads(), hashset! { commit_a.id().clone(), } );} #[test]fn test_rebase_descendants_abandon_and_replace() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B was replaced by commit E. Commit C was abandoned. Commit D should // get rebased onto commit E. // // D // C // E B // |/ // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit();Lines 1–500 of 2010. 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 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::rewrite::find_duplicate_divergent_commits; use jj_lib::rewrite::rebase_commit_with_options; use jj_lib::rewrite::restore_tree; use jj_lib::rewrite::CommitRewriter; use jj_lib::rewrite::CommitWithSelection; use jj_lib::rewrite::EmptyBehaviour; use jj_lib::rewrite::MoveCommitsTarget; use jj_lib::rewrite::RebaseOptions; use jj_lib::rewrite::RewriteRefsOptions; use maplit::hashmap; use maplit::hashset; use pollster::FutureExt as _; use test_case::test_case; use testutils::assert_abandoned_with_parent; use testutils::assert_rebased_onto; 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::CommitGraphBuilder; use testutils::TestRepo; 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(), } } #[test] fn test_restore_tree() { 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, &EverythingMatcher) .block_on() .unwrap(); assert_eq!(restored, left.id()); // Restore everything using FilesMatcher let restored = restore_tree( &left, &right, &FilesMatcher::new([&path1, &path2, &path3, &path4]), ) .block_on() .unwrap(); assert_eq!(restored, left.id()); // Restore some files let restored = restore_tree(&left, &right, &FilesMatcher::new([path1, path2])) .block_on() .unwrap(); let expected = create_tree(repo, &[(path2, "left"), (path3, "right")]); assert_eq!(restored, expected.id()); } #[test] fn test_rebase_descendants_sideways() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B was replaced by commit F. Commits C-E should be rebased. // // F // | D // | C E // | |/ // | B // |/ // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_c]); let commit_e = graph_builder.commit_with_parents(&[&commit_b]); let commit_f = graph_builder.commit_with_parents(&[&commit_a]); tx.repo_mut() .set_rewritten_commit(commit_b.id().clone(), commit_f.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); assert_eq!(rebase_map.len(), 3); let new_commit_c = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_c, &[commit_f.id()]); let new_commit_d = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_d, &[new_commit_c.id()]); let new_commit_e = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_e, &[commit_f.id()]); assert_eq!( *tx.repo().view().heads(), hashset! { new_commit_d.id().clone(), new_commit_e.id().clone() } ); } #[test] fn test_rebase_descendants_forward() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B was replaced by commit F. Commits C and E should be rebased onto F. // Commit D does not get rebased because it's an ancestor of the // destination. Commit G does not get replaced because it's already in // place. // TODO: The above is not what actually happens! The test below shows what // actually happens: D and F also get rebased onto F, so we end up with // duplicates. Consider if it's worth supporting the case above better or if // that decision belongs with the caller (as we currently force it to do by // not supporting it in DescendantRebaser). // // G // F E // |/ // D C // |/ // B // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_b]); let commit_e = graph_builder.commit_with_parents(&[&commit_d]); let commit_f = graph_builder.commit_with_parents(&[&commit_d]); let commit_g = graph_builder.commit_with_parents(&[&commit_f]); tx.repo_mut() .set_rewritten_commit(commit_b.id().clone(), commit_f.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_d = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_d, &[(commit_f.id())]); let new_commit_f = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_f, &[new_commit_d.id()]); let new_commit_c = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_c, &[new_commit_f.id()]); let new_commit_e = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_e, &[new_commit_d.id()]); let new_commit_g = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_g, &[new_commit_f.id()]); assert_eq!(rebase_map.len(), 5); assert_eq!( *tx.repo().view().heads(), hashset! { new_commit_c.id().clone(), new_commit_e.id().clone(), new_commit_g.id().clone(), } ); } #[test] fn test_rebase_descendants_reorder() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit E was replaced by commit D, and commit C was replaced by commit F // (attempting to to reorder C and E), and commit G was replaced by commit // H. // // I // G H // E F // C D // |/ // B // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_b]); let commit_e = graph_builder.commit_with_parents(&[&commit_c]); let commit_f = graph_builder.commit_with_parents(&[&commit_d]); let commit_g = graph_builder.commit_with_parents(&[&commit_e]); let commit_h = graph_builder.commit_with_parents(&[&commit_f]); let commit_i = graph_builder.commit_with_parents(&[&commit_g]); tx.repo_mut() .set_rewritten_commit(commit_e.id().clone(), commit_d.id().clone()); tx.repo_mut() .set_rewritten_commit(commit_c.id().clone(), commit_f.id().clone()); tx.repo_mut() .set_rewritten_commit(commit_g.id().clone(), commit_h.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_i = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_i, &[commit_h.id()]); assert_eq!(rebase_map.len(), 1); assert_eq!( *tx.repo().view().heads(), hashset! { new_commit_i.id().clone(), } ); } #[test] fn test_rebase_descendants_backward() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit C was replaced by commit B. Commit D should be rebased. // // D // C // B // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_c]); tx.repo_mut() .set_rewritten_commit(commit_c.id().clone(), commit_b.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_d = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_d, &[commit_b.id()]); assert_eq!(rebase_map.len(), 1); assert_eq!( *tx.repo().view().heads(), hashset! {new_commit_d.id().clone()} ); } #[test] fn test_rebase_descendants_chain_becomes_branchy() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B was replaced by commit E and commit C was replaced by commit F. // Commit F should get rebased onto E, and commit D should get rebased onto // the rebased F. // // D // C F // |/ // B E // |/ // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_c]); let commit_e = graph_builder.commit_with_parents(&[&commit_a]); let commit_f = graph_builder.commit_with_parents(&[&commit_b]); tx.repo_mut() .set_rewritten_commit(commit_b.id().clone(), commit_e.id().clone()); tx.repo_mut() .set_rewritten_commit(commit_c.id().clone(), commit_f.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_f = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_f, &[commit_e.id()]); let new_commit_d = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_d, &[new_commit_f.id()]); assert_eq!(rebase_map.len(), 2); assert_eq!( *tx.repo().view().heads(), hashset! { new_commit_d.id().clone(), } ); } #[test] fn test_rebase_descendants_internal_merge() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B was replaced by commit F. Commits C-E should be rebased. // // F // | E // | |\ // | C D // | |/ // | B // |/ // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_b]); let commit_e = graph_builder.commit_with_parents(&[&commit_c, &commit_d]); let commit_f = graph_builder.commit_with_parents(&[&commit_a]); tx.repo_mut() .set_rewritten_commit(commit_b.id().clone(), commit_f.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_c = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_c, &[commit_f.id()]); let new_commit_d = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_d, &[commit_f.id()]); let new_commit_e = assert_rebased_onto( tx.repo_mut(), &rebase_map, &commit_e, &[new_commit_c.id(), new_commit_d.id()], ); assert_eq!(rebase_map.len(), 3); assert_eq!( *tx.repo().view().heads(), hashset! { new_commit_e.id().clone() } ); } #[test] fn test_rebase_descendants_external_merge() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit C was replaced by commit F. Commits E should be rebased. The rebased // commit E should have F as first parent and commit D as second parent. // // F // | E // | |\ // | C D // | |/ // | B // |/ // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_b]); let commit_e = graph_builder.commit_with_parents(&[&commit_c, &commit_d]); let commit_f = graph_builder.commit_with_parents(&[&commit_a]); tx.repo_mut() .set_rewritten_commit(commit_c.id().clone(), commit_f.id().clone()); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_e = assert_rebased_onto( tx.repo_mut(), &rebase_map, &commit_e, &[commit_f.id(), commit_d.id()], ); assert_eq!(rebase_map.len(), 1); assert_eq!( *tx.repo().view().heads(), hashset! {new_commit_e.id().clone()} ); } #[test] fn test_rebase_descendants_abandon() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B and commit E were abandoned. Commit C and commit D should get // rebased onto commit A. Commit F should get rebased onto the new commit D. // // F // E // D C // |/ // B // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); let commit_d = graph_builder.commit_with_parents(&[&commit_b]); let commit_e = graph_builder.commit_with_parents(&[&commit_d]); let commit_f = graph_builder.commit_with_parents(&[&commit_e]); tx.repo_mut().record_abandoned_commit(&commit_b); tx.repo_mut().record_abandoned_commit(&commit_e); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); let new_commit_c = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_c, &[commit_a.id()]); let new_commit_d = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_d, &[commit_a.id()]); let new_commit_f = assert_rebased_onto(tx.repo_mut(), &rebase_map, &commit_f, &[new_commit_d.id()]); assert_eq!(rebase_map.len(), 3); assert_eq!( *tx.repo().view().heads(), hashset! { new_commit_c.id().clone(), new_commit_f.id().clone() } ); } #[test] fn test_rebase_descendants_abandon_no_descendants() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B and C were abandoned. Commit A should become a head. // // C // B // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit(); let commit_b = graph_builder.commit_with_parents(&[&commit_a]); let commit_c = graph_builder.commit_with_parents(&[&commit_b]); tx.repo_mut().record_abandoned_commit(&commit_b); tx.repo_mut().record_abandoned_commit(&commit_c); let rebase_map = rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default()); assert_eq!(rebase_map.len(), 0); assert_eq!( *tx.repo().view().heads(), hashset! { commit_a.id().clone(), } ); } #[test] fn test_rebase_descendants_abandon_and_replace() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // Commit B was replaced by commit E. Commit C was abandoned. Commit D should // get rebased onto commit E. // // D // C // E B // |/ // A let mut tx = repo.start_transaction(); let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut()); let commit_a = graph_builder.initial_commit();