1070 lines · 35.4 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 std::path::Path;use std::slice;use std::sync::Arc;use std::time::SystemTime; use assert_matches::assert_matches;use itertools::Itertools as _;use jj_lib::backend::CommitId;use jj_lib::config::ConfigLayer;use jj_lib::config::ConfigSource;use jj_lib::evolution::walk_predecessors;use jj_lib::object_id::ObjectId as _;use jj_lib::op_store::OperationId;use jj_lib::op_walk;use jj_lib::op_walk::OpsetEvaluationError;use jj_lib::op_walk::OpsetResolutionError;use jj_lib::operation::Operation;use jj_lib::repo::ReadonlyRepo;use jj_lib::repo::Repo;use jj_lib::settings::UserSettings;use test_case::test_case;use testutils::create_random_commit;use testutils::write_random_commit;use testutils::CommitGraphBuilder;use testutils::TestRepo; fn get_predecessors(repo: &ReadonlyRepo, id: &CommitId) -> Vec<CommitId> {    let entries: Vec<_> = walk_predecessors(repo, slice::from_ref(id))        .try_collect()        .expect("unreachable predecessors shouldn't be visited");    let first = entries        .first()        .expect("specified commit should be reachable");    first.predecessor_ids().to_vec()} fn list_dir(dir: &Path) -> Vec<String> {    std::fs::read_dir(dir)        .unwrap()        .map(|entry| entry.unwrap().file_name().to_str().unwrap().to_owned())        .sorted()        .collect()} #[test]fn test_unpublished_operation() {    // Test that the operation doesn't get published until that's requested.    let test_repo = TestRepo::init();    let repo = &test_repo.repo;     let op_heads_dir = test_repo.repo_path().join("op_heads").join("heads");    let op_id0 = repo.op_id().clone();    assert_eq!(list_dir(&op_heads_dir), vec![repo.op_id().hex()]);     let mut tx1 = repo.start_transaction();    write_random_commit(tx1.repo_mut());    let unpublished_op = tx1.write("transaction 1").unwrap();    let op_id1 = unpublished_op.operation().id().clone();    assert_ne!(op_id1, op_id0);    assert_eq!(list_dir(&op_heads_dir), vec![op_id0.hex()]);    unpublished_op.publish().unwrap();    assert_eq!(list_dir(&op_heads_dir), vec![op_id1.hex()]);} #[test]fn test_consecutive_operations() {    // Test that consecutive operations result in a single op-head on disk after    // each operation    let test_repo = TestRepo::init();    let repo = &test_repo.repo;     let op_heads_dir = test_repo.repo_path().join("op_heads").join("heads");    let op_id0 = repo.op_id().clone();    assert_eq!(list_dir(&op_heads_dir), vec![repo.op_id().hex()]);     let mut tx1 = repo.start_transaction();    write_random_commit(tx1.repo_mut());    let op_id1 = tx1        .commit("transaction 1")        .unwrap()        .operation()        .id()        .clone();    assert_ne!(op_id1, op_id0);    assert_eq!(list_dir(&op_heads_dir), vec![op_id1.hex()]);     let repo = repo.reload_at_head().unwrap();    let mut tx2 = repo.start_transaction();    write_random_commit(tx2.repo_mut());    let op_id2 = tx2        .commit("transaction 2")        .unwrap()        .operation()        .id()        .clone();    assert_ne!(op_id2, op_id0);    assert_ne!(op_id2, op_id1);    assert_eq!(list_dir(&op_heads_dir), vec![op_id2.hex()]);     // Reloading the repo makes no difference (there are no conflicting operations    // to resolve).    let _repo = repo.reload_at_head().unwrap();    assert_eq!(list_dir(&op_heads_dir), vec![op_id2.hex()]);} #[test]fn test_concurrent_operations() {    // Test that consecutive operations result in multiple op-heads on disk until    // the repo has been reloaded (which currently happens right away).    let test_repo = TestRepo::init();    let repo = &test_repo.repo;     let op_heads_dir = test_repo.repo_path().join("op_heads").join("heads");    let op_id0 = repo.op_id().clone();    assert_eq!(list_dir(&op_heads_dir), vec![repo.op_id().hex()]);     let mut tx1 = repo.start_transaction();    write_random_commit(tx1.repo_mut());    let op_id1 = tx1        .commit("transaction 1")        .unwrap()        .operation()        .id()        .clone();    assert_ne!(op_id1, op_id0);    assert_eq!(list_dir(&op_heads_dir), vec![op_id1.hex()]);     // After both transactions have committed, we should have two op-heads on disk,    // since they were run in parallel.    let mut tx2 = repo.start_transaction();    write_random_commit(tx2.repo_mut());    let op_id2 = tx2        .commit("transaction 2")        .unwrap()        .operation()        .id()        .clone();    assert_ne!(op_id2, op_id0);    assert_ne!(op_id2, op_id1);    let mut actual_heads_on_disk = list_dir(&op_heads_dir);    actual_heads_on_disk.sort();    let mut expected_heads_on_disk = vec![op_id1.hex(), op_id2.hex()];    expected_heads_on_disk.sort();    assert_eq!(actual_heads_on_disk, expected_heads_on_disk);     // Reloading the repo causes the operations to be merged    let repo = repo.reload_at_head().unwrap();    let merged_op_id = repo.op_id().clone();    assert_ne!(merged_op_id, op_id0);    assert_ne!(merged_op_id, op_id1);    assert_ne!(merged_op_id, op_id2);    assert_eq!(list_dir(&op_heads_dir), vec![merged_op_id.hex()]);} fn assert_heads(repo: &dyn Repo, expected: Vec<&CommitId>) {    let expected = expected.iter().cloned().cloned().collect();    assert_eq!(*repo.view().heads(), expected);} #[test]fn test_isolation() {    // Test that two concurrent transactions don't see each other's changes.    let test_repo = TestRepo::init();    let repo = &test_repo.repo;     let mut tx = repo.start_transaction();    let initial = create_random_commit(tx.repo_mut())        .set_parents(vec![repo.store().root_commit_id().clone()])        .write()        .unwrap();    let repo = tx.commit("test").unwrap();     let mut tx1 = repo.start_transaction();    let mut_repo1 = tx1.repo_mut();    let mut tx2 = repo.start_transaction();    let mut_repo2 = tx2.repo_mut();     assert_heads(repo.as_ref(), vec![initial.id()]);    assert_heads(mut_repo1, vec![initial.id()]);    assert_heads(mut_repo2, vec![initial.id()]);     let rewrite1 = mut_repo1        .rewrite_commit(&initial)        .set_description("rewrite1")        .write()        .unwrap();    mut_repo1.rebase_descendants().unwrap();    let rewrite2 = mut_repo2        .rewrite_commit(&initial)        .set_description("rewrite2")        .write()        .unwrap();    mut_repo2.rebase_descendants().unwrap();     // Neither transaction has committed yet, so each transaction sees its own    // commit.    assert_heads(repo.as_ref(), vec![initial.id()]);    assert_heads(mut_repo1, vec![rewrite1.id()]);    assert_heads(mut_repo2, vec![rewrite2.id()]);     // The base repo and tx2 don't see the commits from tx1.    tx1.commit("transaction 1").unwrap();    assert_heads(repo.as_ref(), vec![initial.id()]);    assert_heads(mut_repo2, vec![rewrite2.id()]);     // The base repo still doesn't see the commits after both transactions commit.    tx2.commit("transaction 2").unwrap();    assert_heads(repo.as_ref(), vec![initial.id()]);    // After reload, the base repo sees both rewrites.    let repo = repo.reload_at_head().unwrap();    assert_heads(repo.as_ref(), vec![rewrite1.id(), rewrite2.id()]);} #[test]fn test_stored_commit_predecessors() {    let test_repo = TestRepo::init();    let repo = &test_repo.repo;    let loader = repo.loader();     let mut tx = repo.start_transaction();    let commit1 = create_random_commit(tx.repo_mut()).write().unwrap();    let commit2 = tx        .repo_mut()        .rewrite_commit(&commit1)        .set_description("rewritten")        .write()        .unwrap();    tx.repo_mut().rebase_descendants().unwrap();    let repo = tx.commit("test").unwrap();     // Reload operation from disk.    let op = loader.load_operation(repo.op_id()).unwrap();    assert!(op.stores_commit_predecessors());    assert_matches!(op.predecessors_for_commit(commit1.id()), Some([]));    assert_matches!(op.predecessors_for_commit(commit2.id()), Some([id]) if id == commit1.id());     // Save operation without the predecessors as old jj would do.    let mut data = op.store_operation().clone();    data.commit_predecessors = None;    let op_id = loader.op_store().write_operation(&data).unwrap();    assert_ne!(&op_id, op.id());    let op = loader.load_operation(&op_id).unwrap();    assert!(!op.stores_commit_predecessors());} #[test]fn test_reparent_range_linear() {    let test_repo = TestRepo::init();    let repo_0 = test_repo.repo;    let loader = repo_0.loader();    let op_store = repo_0.op_store();     let read_op = |id| loader.load_operation(id).unwrap();     fn op_parents<const N: usize>(op: &Operation) -> [Operation; N] {        let parents: Vec<_> = op.parents().try_collect().unwrap();        parents.try_into().unwrap()    }     // Set up linear operation graph:    // D    // C    // B    // A    // 0 (initial)    let random_tx = |repo: &Arc<ReadonlyRepo>| {        let mut tx = repo.start_transaction();        write_random_commit(tx.repo_mut());        tx    };    let repo_a = random_tx(&repo_0).commit("op A").unwrap();    let repo_b = random_tx(&repo_a).commit("op B").unwrap();    let repo_c = random_tx(&repo_b).commit("op C").unwrap();    let repo_d = random_tx(&repo_c).commit("op D").unwrap();     // Reparent B..D (=C|D) onto A:    // D'    // C'    // A    // 0 (initial)    let stats = op_walk::reparent_range(        op_store.as_ref(),        slice::from_ref(repo_b.operation()),        slice::from_ref(repo_d.operation()),        repo_a.operation(),    )    .unwrap();    assert_eq!(stats.new_head_ids.len(), 1);    assert_eq!(stats.rewritten_count, 2);    assert_eq!(stats.unreachable_count, 1);    let new_op_d = read_op(&stats.new_head_ids[0]);    assert_eq!(new_op_d.metadata(), repo_d.operation().metadata());    assert_eq!(new_op_d.view_id(), repo_d.operation().view_id());    let [new_op_c] = op_parents(&new_op_d);    assert_eq!(new_op_c.metadata(), repo_c.operation().metadata());    assert_eq!(new_op_c.view_id(), repo_c.operation().view_id());    assert_eq!(new_op_c.parent_ids(), slice::from_ref(repo_a.op_id()));     // Reparent empty range onto A    let stats = op_walk::reparent_range(        op_store.as_ref(),        slice::from_ref(repo_d.operation()),        slice::from_ref(repo_d.operation()),        repo_a.operation(),    )    .unwrap();    assert_eq!(stats.new_head_ids, vec![repo_a.op_id().clone()]);    assert_eq!(stats.rewritten_count, 0);    assert_eq!(stats.unreachable_count, 3);} #[test]fn test_reparent_range_branchy() {    let test_repo = TestRepo::init();    let repo_0 = test_repo.repo;    let loader = repo_0.loader();    let op_store = repo_0.op_store();     let read_op = |id| loader.load_operation(id).unwrap();     fn op_parents<const N: usize>(op: &Operation) -> [Operation; N] {        let parents: Vec<_> = op.parents().try_collect().unwrap();        parents.try_into().unwrap()    }     // Set up branchy operation graph:    // G    // |\    // | F    // E |    // D |    // |/    // C    // B    // A    // 0 (initial)    let random_tx = |repo: &Arc<ReadonlyRepo>| {        let mut tx = repo.start_transaction();        write_random_commit(tx.repo_mut());        tx    };    let repo_a = random_tx(&repo_0).commit("op A").unwrap();    let repo_b = random_tx(&repo_a).commit("op B").unwrap();    let repo_c = random_tx(&repo_b).commit("op C").unwrap();    let repo_d = random_tx(&repo_c).commit("op D").unwrap();    let tx_e = random_tx(&repo_d);    let tx_f = random_tx(&repo_c);    let repo_g = testutils::commit_transactions(vec![tx_e, tx_f]);    let [op_e, op_f] = op_parents(repo_g.operation());     // Reparent D..G (= E|F|G) onto B:    // G'    // |\    // | F'    // E'|    // |/    // B    // A    // 0 (initial)    let stats = op_walk::reparent_range(        op_store.as_ref(),        slice::from_ref(repo_d.operation()),        slice::from_ref(repo_g.operation()),        repo_b.operation(),    )    .unwrap();    assert_eq!(stats.new_head_ids.len(), 1);    assert_eq!(stats.rewritten_count, 3);    assert_eq!(stats.unreachable_count, 2);    let new_op_g = read_op(&stats.new_head_ids[0]);    assert_eq!(new_op_g.metadata(), repo_g.operation().metadata());    assert_eq!(new_op_g.view_id(), repo_g.operation().view_id());    let [new_op_e, new_op_f] = op_parents(&new_op_g);    assert_eq!(new_op_e.parent_ids(), slice::from_ref(repo_b.op_id()));    assert_eq!(new_op_f.parent_ids(), slice::from_ref(repo_b.op_id()));     // Reparent B..G (=C|D|E|F|G) onto A:    // G'    // |\    // | F'    // E'|    // D'|    // |/    // C'    // A    // 0 (initial)    let stats = op_walk::reparent_range(        op_store.as_ref(),        slice::from_ref(repo_b.operation()),        slice::from_ref(repo_g.operation()),        repo_a.operation(),    )    .unwrap();    assert_eq!(stats.new_head_ids.len(), 1);    assert_eq!(stats.rewritten_count, 5);    assert_eq!(stats.unreachable_count, 1);    let new_op_g = read_op(&stats.new_head_ids[0]);    assert_eq!(new_op_g.metadata(), repo_g.operation().metadata());    assert_eq!(new_op_g.view_id(), repo_g.operation().view_id());    let [new_op_e, new_op_f] = op_parents(&new_op_g);    let [new_op_d] = op_parents(&new_op_e);    assert_eq!(new_op_d.parent_ids(), new_op_f.parent_ids());    let [new_op_c] = op_parents(&new_op_d);    assert_eq!(new_op_c.parent_ids(), slice::from_ref(repo_a.op_id()));     // Reparent (E|F)..G (=G) onto D:    // G'    // D    // C    // B    // A    // 0 (initial)    let stats = op_walk::reparent_range(        op_store.as_ref(),        &[op_e.clone(), op_f.clone()],        slice::from_ref(repo_g.operation()),        repo_d.operation(),    )    .unwrap();    assert_eq!(stats.new_head_ids.len(), 1);    assert_eq!(stats.rewritten_count, 1);    assert_eq!(stats.unreachable_count, 2);    let new_op_g = read_op(&stats.new_head_ids[0]);    assert_eq!(new_op_g.metadata(), repo_g.operation().metadata());    assert_eq!(new_op_g.view_id(), repo_g.operation().view_id());    assert_eq!(new_op_g.parent_ids(), slice::from_ref(repo_d.op_id()));     // Reparent C..F (=F) onto D (ignoring G):    // F'    // D    // C    // B    // A    // 0 (initial)    let stats = op_walk::reparent_range(        op_store.as_ref(),        slice::from_ref(repo_c.operation()),        slice::from_ref(&op_f),        repo_d.operation(),    )    .unwrap();    assert_eq!(stats.new_head_ids.len(), 1);    assert_eq!(stats.rewritten_count, 1);    assert_eq!(stats.unreachable_count, 0);    let new_op_f = read_op(&stats.new_head_ids[0]);    assert_eq!(new_op_f.metadata(), op_f.metadata());    assert_eq!(new_op_f.view_id(), op_f.view_id());    assert_eq!(new_op_f.parent_ids(), slice::from_ref(repo_d.op_id()));} #[test_case(false; "legacy commit.predecessors")]#[test_case(true; "op.commit_predecessors")]fn test_reparent_discarding_predecessors(op_stores_commit_predecessors: bool) {    let test_repo = TestRepo::init();    let repo_0 = test_repo.repo;    let loader = repo_0.loader();    let op_store = repo_0.op_store();     let repo_at = |id: &OperationId| {        let op = loader.load_operation(id).unwrap();        loader.load_at(&op).unwrap()    };    let head_commits = |repo: &dyn Repo| {        repo.view()            .heads()            .iter()            .map(|id| repo.store().get_commit(id).unwrap())            .collect_vec()    };     // Set up rewriting as follows:    //    //   op1     op2     op3     op4    //   B0      B0 B1      B1    //   |       |  |       |    //   A0      A0 A1   A0 A1   A2    let mut tx = repo_0.start_transaction();    let mut graph_builder = CommitGraphBuilder::new(tx.repo_mut());    let commit_a0 = graph_builder.initial_commit();    let commit_b0 = graph_builder.commit_with_parents(&[&commit_a0]);    let repo_1 = tx.commit("op1").unwrap();     let mut tx = repo_1.start_transaction();    let commit_a1 = tx        .repo_mut()        .rewrite_commit(&commit_a0)

Lines 1–500 of 1070.