276 lines · 9.5 KB · Rust
RawDownload
// Copyright 2020 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::MergedTreeId;use jj_lib::backend::TreeValue;use jj_lib::config::ConfigLayer;use jj_lib::config::ConfigSource;use jj_lib::merge::Merge;use jj_lib::merge::SameChange;use jj_lib::repo::Repo as _;use jj_lib::rewrite::rebase_commit;use jj_lib::settings::UserSettings;use pollster::FutureExt as _;use test_case::test_case;use testutils::TestRepo;use testutils::create_tree;use testutils::repo_path; #[test]fn test_simplify_conflict_after_resolving_parent() {    let test_repo = TestRepo::init();    let repo = &test_repo.repo;     // Set up a repo like this:    // D    // | C    // | B    // |/    // A    //    // Commit A has a file with 3 lines. B and D make conflicting changes to the    // first line. C changes the third line. We then rebase B and C onto D,    // which creates a conflict. We resolve the conflict in the first line and    // rebase C2 (the rebased C) onto the resolved conflict. C3 should not have    // a conflict since it changed an unrelated line.    let path = repo_path("dir/file");    let mut tx = repo.start_transaction();    let tree_a = create_tree(repo, &[(path, "abc\ndef\nghi\n")]);    let commit_a = tx        .repo_mut()        .new_commit(vec![repo.store().root_commit_id().clone()], tree_a.id())        .write()        .unwrap();    let tree_b = create_tree(repo, &[(path, "Abc\ndef\nghi\n")]);    let commit_b = tx        .repo_mut()        .new_commit(vec![commit_a.id().clone()], tree_b.id())        .write()        .unwrap();    let tree_c = create_tree(repo, &[(path, "Abc\ndef\nGhi\n")]);    let commit_c = tx        .repo_mut()        .new_commit(vec![commit_b.id().clone()], tree_c.id())        .write()        .unwrap();    let tree_d = create_tree(repo, &[(path, "abC\ndef\nghi\n")]);    let commit_d = tx        .repo_mut()        .new_commit(vec![commit_a.id().clone()], tree_d.id())        .write()        .unwrap();     let commit_b2 = rebase_commit(tx.repo_mut(), commit_b, vec![commit_d.id().clone()])        .block_on()        .unwrap();    let commit_c2 = rebase_commit(tx.repo_mut(), commit_c, vec![commit_b2.id().clone()])        .block_on()        .unwrap();     // Test the setup: Both B and C should have conflicts.    let tree_b2 = commit_b2.tree().unwrap();    let tree_c2 = commit_b2.tree().unwrap();    assert!(!tree_b2.path_value(path).unwrap().is_resolved());    assert!(!tree_c2.path_value(path).unwrap().is_resolved());     // Create the resolved B and rebase C on top.    let tree_b3 = create_tree(repo, &[(path, "AbC\ndef\nghi\n")]);    let commit_b3 = tx        .repo_mut()        .rewrite_commit(&commit_b2)        .set_tree_id(tree_b3.id())        .write()        .unwrap();    let commit_c3 = rebase_commit(tx.repo_mut(), commit_c2, vec![commit_b3.id().clone()])        .block_on()        .unwrap();    tx.repo_mut().rebase_descendants().unwrap();    let repo = tx.commit("test").unwrap();     // The conflict should now be resolved.    let tree_c2 = commit_c3.tree().unwrap();    let resolved_value = tree_c2.path_value(path).unwrap();    match resolved_value.into_resolved() {        Ok(Some(TreeValue::File {            id,            executable: false,            copy_id: _,        })) => {            assert_eq!(                testutils::read_file(repo.store(), path, &id),                b"AbC\ndef\nGhi\n"            );        }        other => {            panic!("unexpected value: {other:#?}");        }    }} // TODO: Add tests for simplification of multi-way conflicts. Both the content// and the executable bit need testing. #[test_case(SameChange::Keep)]#[test_case(SameChange::Accept)]fn test_rebase_linearize_lossy_merge(same_change: SameChange) {    let settings = settings_with_same_change(same_change);    let test_repo = TestRepo::init_with_settings(&settings);    let repo = &test_repo.repo;     // Test this rebase:    // D    foo=2          D' foo=1 or 2    // |\                  |    // | C  foo=2          |    // | |           =>    B  foo=2    // B |  foo=2          |    // |/                  |    // A    foo=1          A  foo=1    //    // Since both B and C changed "1" to "2" but only one "2" remains in D, it    // effectively discarded a change from "1" to "2". With `SameChange::Keep`,    // D' is therefore "1". However, with `SameChange::Accept`, `jj show D` etc.    // currently don't tell the user about the discarded change, so it's    // surprising that the change in commit D is interpreted that way.    let path = repo_path("foo");    let mut tx = repo.start_transaction();    let repo_mut = tx.repo_mut();    let tree_1 = create_tree(repo, &[(path, "1")]);    let tree_2 = create_tree(repo, &[(path, "2")]);    let commit_a = repo_mut        .new_commit(vec![repo.store().root_commit_id().clone()], tree_1.id())        .write()        .unwrap();    let commit_b = repo_mut        .new_commit(vec![commit_a.id().clone()], tree_2.id())        .write()        .unwrap();    let commit_c = repo_mut        .new_commit(vec![commit_a.id().clone()], tree_2.id())        .write()        .unwrap();    let commit_d = repo_mut        .new_commit(            vec![commit_b.id().clone(), commit_c.id().clone()],            tree_2.id(),        )        .write()        .unwrap();     match same_change {        SameChange::Keep => assert!(!commit_d.is_empty(repo_mut).unwrap()),        SameChange::Accept => assert!(commit_d.is_empty(repo_mut).unwrap()),    }     let commit_d2 = rebase_commit(repo_mut, commit_d, vec![commit_b.id().clone()])        .block_on()        .unwrap();     match same_change {        SameChange::Keep => assert_eq!(*commit_d2.tree_id(), tree_1.id()),        SameChange::Accept => assert_eq!(*commit_d2.tree_id(), tree_2.id()),    }} #[test_case(SameChange::Keep)]#[test_case(SameChange::Accept)]fn test_rebase_on_lossy_merge(same_change: SameChange) {    let settings = settings_with_same_change(same_change);    let test_repo = TestRepo::init_with_settings(&settings);    let repo = &test_repo.repo;     // Test this rebase:    // D    foo=2          D'   foo=3 or 2+(3-1) (conflict)    // |\                  |\    // | C  foo=2          | C' foo=3    // | |           =>    | |    // B |  foo=2          B |  foo=2    // |/                  |/    // A    foo=1          A    foo=1    //    // Commit D effectively discarded a change from "1" to "2", so one    // reasonable result in D' is "3". That's the result with    // `SameChange::Keep`. However, with `SameChange::Accept`, we resolve the    // auto-merged parents to just "2" before the rebase in order to be    // consistent with `jj show D` and other commands for inspecting the commit,    // so we instead get a conflict after the rebase.    let path = repo_path("foo");    let mut tx = repo.start_transaction();    let repo_mut = tx.repo_mut();    let tree_1 = create_tree(repo, &[(path, "1")]);    let tree_2 = create_tree(repo, &[(path, "2")]);    let tree_3 = create_tree(repo, &[(path, "3")]);    let commit_a = repo_mut        .new_commit(vec![repo.store().root_commit_id().clone()], tree_1.id())        .write()        .unwrap();    let commit_b = repo_mut        .new_commit(vec![commit_a.id().clone()], tree_2.id())        .write()        .unwrap();    let commit_c = repo_mut        .new_commit(vec![commit_a.id().clone()], tree_2.id())        .write()        .unwrap();    let commit_d = repo_mut        .new_commit(            vec![commit_b.id().clone(), commit_c.id().clone()],            tree_2.id(),        )        .write()        .unwrap();     match same_change {        SameChange::Keep => assert!(!commit_d.is_empty(repo_mut).unwrap()),        SameChange::Accept => assert!(commit_d.is_empty(repo_mut).unwrap()),    }     let commit_c2 = repo_mut        .new_commit(vec![commit_a.id().clone()], tree_3.id())        .write()        .unwrap();    let commit_d2 = rebase_commit(        repo_mut,        commit_d,        vec![commit_b.id().clone(), commit_c2.id().clone()],    )    .block_on()    .unwrap();     match same_change {        SameChange::Keep => assert_eq!(*commit_d2.tree_id(), tree_3.id()),        SameChange::Accept => {            let expected_tree_id = Merge::from_vec(vec![                tree_2.id().into_merge(),                tree_1.id().into_merge(),                tree_3.id().into_merge(),            ])            .flatten();            assert_eq!(*commit_d2.tree_id(), MergedTreeId::new(expected_tree_id));        }    }} fn settings_with_same_change(same_change: SameChange) -> UserSettings {    let mut config = testutils::base_user_config();    let mut layer = ConfigLayer::empty(ConfigSource::User);    let same_change_str = match same_change {        SameChange::Keep => "keep",        SameChange::Accept => "accept",    };    layer        .set_value("merge.same-change", same_change_str)        .unwrap();    config.add_layer(layer);    UserSettings::from_config(config).unwrap()}