1063 lines · 35.1 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 futures::TryStreamExt as _;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::index::Index;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 pollster::FutureExt as _;use testutils::CommitBuilderExt as _;use testutils::TestRepo;use testutils::TestResult;use testutils::write_random_commit;use testutils::write_random_commit_with_parents; fn get_predecessors(repo: &ReadonlyRepo, id: &CommitId) -> Vec<CommitId> {    let entries: Vec<_> = walk_predecessors(repo, slice::from_ref(id))        .try_collect()        .block_on()        .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()} fn index_has_id(index: &dyn Index, commit_id: &CommitId) -> bool {    index.has_id(commit_id).unwrap()} #[test]fn test_unpublished_operation() -> TestResult {    // 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").block_on()?;    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().block_on()?;    assert_eq!(list_dir(&op_heads_dir), vec![op_id1.hex()]);    Ok(())} #[test]fn test_consecutive_operations() -> TestResult {    // 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")        .block_on()?        .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().block_on()?;    let mut tx2 = repo.start_transaction();    write_random_commit(tx2.repo_mut());    let op_id2 = tx2        .commit("transaction 2")        .block_on()?        .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().block_on()?;    assert_eq!(list_dir(&op_heads_dir), vec![op_id2.hex()]);    Ok(())} #[test]fn test_concurrent_operations() -> TestResult {    // 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")        .block_on()?        .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")        .block_on()?        .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().block_on()?;    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()]);    Ok(())} fn assert_heads(repo: &dyn Repo, expected: Vec<&CommitId>) {    let expected = expected.iter().copied().cloned().collect();    assert_eq!(*repo.view().heads(), expected);} #[test]fn test_isolation() -> TestResult {    // 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 = write_random_commit_with_parents(tx.repo_mut(), &[]);    let repo = tx.commit("test").block_on()?;     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().block_on()?;    let rewrite2 = mut_repo2        .rewrite_commit(&initial)        .set_description("rewrite2")        .write_unwrap();    mut_repo2.rebase_descendants().block_on()?;     // 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").block_on()?;    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").block_on()?;    assert_heads(repo.as_ref(), vec![initial.id()]);    // After reload, the base repo sees both rewrites.    let repo = repo.reload_at_head().block_on()?;    assert_heads(repo.as_ref(), vec![rewrite1.id(), rewrite2.id()]);    Ok(())} #[test]fn test_stored_commit_predecessors() -> TestResult {    let test_repo = TestRepo::init();    let repo = &test_repo.repo;    let loader = repo.loader();     let mut tx = repo.start_transaction();    let commit1 = write_random_commit(tx.repo_mut());    let commit2 = tx        .repo_mut()        .rewrite_commit(&commit1)        .set_description("rewritten")        .write_unwrap();    tx.repo_mut().rebase_descendants().block_on()?;    let repo = tx.commit("test").block_on()?;     // Reload operation from disk.    let op = loader.load_operation(repo.op_id()).block_on()?;    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).block_on()?;    assert_ne!(&op_id, op.id());    let op = loader.load_operation(&op_id).block_on()?;    assert!(!op.stores_commit_predecessors());    Ok(())} #[test]fn test_reparent_range_linear() -> TestResult {    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).block_on().unwrap();     fn op_parents<const N: usize>(op: &Operation) -> [Operation; N] {        let parents = op.parents().block_on().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").block_on()?;    let repo_b = random_tx(&repo_a).commit("op B").block_on()?;    let repo_c = random_tx(&repo_b).commit("op C").block_on()?;    let repo_d = random_tx(&repo_c).commit("op D").block_on()?;     // 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(),    )    .block_on()?;    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(),    )    .block_on()?;    assert_eq!(stats.new_head_ids, vec![repo_a.op_id().clone()]);    assert_eq!(stats.rewritten_count, 0);    assert_eq!(stats.unreachable_count, 3);    Ok(())} #[test]fn test_reparent_range_branchy() -> TestResult {    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).block_on().unwrap();     fn op_parents<const N: usize>(op: &Operation) -> [Operation; N] {        let parents = op.parents().block_on().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").block_on()?;    let repo_b = random_tx(&repo_a).commit("op B").block_on()?;    let repo_c = random_tx(&repo_b).commit("op C").block_on()?;    let repo_d = random_tx(&repo_c).commit("op D").block_on()?;    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(),    )    .block_on()?;    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(),    )    .block_on()?;    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(),    )    .block_on()?;    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(),    )    .block_on()?;    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()));    Ok(())} #[test]fn test_reparent_discarding_predecessors() -> TestResult {    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).block_on().unwrap();        loader.load_at(&op).block_on().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 commit_a0 = write_random_commit(tx.repo_mut());

Lines 1–500 of 1063.