1894 lines · 66.8 KB · Rust
RawDownload
// 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::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::repo_path::RepoPath;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::RebaseOptions;use jj_lib::rewrite::RewriteRefsOptions;use maplit::hashmap;use maplit::hashset;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::rebase_descendants_with_options_return_map;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 = RepoPath::from_internal_string("file1");    let path2 = RepoPath::from_internal_string("dir1/file2");    let path3 = RepoPath::from_internal_string("dir1/file3");    let path4 = RepoPath::from_internal_string("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).unwrap();    assert_eq!(restored, left.id());     // Restore everything using FilesMatcher    let restored = restore_tree(        &left,        &right,        &FilesMatcher::new([&path1, &path2, &path3, &path4]),    )    .unwrap();    assert_eq!(restored, left.id());     // Restore some files    let restored = restore_tree(&left, &right, &FilesMatcher::new([path1, path2])).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();    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]);     tx.repo_mut()        .set_rewritten_commit(commit_b.id().clone(), commit_e.id().clone());    tx.repo_mut().record_abandoned_commit(&commit_c);    let rebase_map =        rebase_descendants_with_options_return_map(tx.repo_mut(), &RebaseOptions::default());

Lines 1–500 of 1894.