mirror of
https://github.com/martinvonz/jj.git
synced 2024-12-04 05:28:02 +00:00
5f74dd5db3
I'd like to be able to pass a `self` of `type `&ReadonlyRepo` to functions that take a `&dyn Repo`. For that, we need `ReadonlyRepo` itself to implement `Repo` instead of having `Arc<ReadonlyRepo>` implement it. I could have solved it in a different way, but the `Arc` requirement seems like an unnecessary constraint.
184 lines
7.2 KiB
Rust
184 lines
7.2 KiB
Rust
// 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 jujutsu_lib::backend::CommitId;
|
|
use jujutsu_lib::repo::Repo;
|
|
use test_case::test_case;
|
|
use testutils::{create_random_commit, write_random_commit, TestRepo};
|
|
|
|
fn list_dir(dir: &Path) -> Vec<String> {
|
|
std::fs::read_dir(dir)
|
|
.unwrap()
|
|
.map(|entry| entry.unwrap().file_name().to_str().unwrap().to_owned())
|
|
.collect()
|
|
}
|
|
|
|
#[test_case(false ; "local backend")]
|
|
#[test_case(true ; "git backend")]
|
|
fn test_unpublished_operation(use_git: bool) {
|
|
// Test that the operation doesn't get published until that's requested.
|
|
let settings = testutils::user_settings();
|
|
let test_repo = TestRepo::init(use_git);
|
|
let repo = &test_repo.repo;
|
|
|
|
let op_heads_dir = 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(&settings, "transaction 1");
|
|
write_random_commit(tx1.mut_repo(), &settings);
|
|
let unpublished_op = tx1.write();
|
|
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();
|
|
assert_eq!(list_dir(&op_heads_dir), vec![op_id1.hex()]);
|
|
}
|
|
|
|
#[test_case(false ; "local backend")]
|
|
#[test_case(true ; "git backend")]
|
|
fn test_consecutive_operations(use_git: bool) {
|
|
// Test that consecutive operations result in a single op-head on disk after
|
|
// each operation
|
|
let settings = testutils::user_settings();
|
|
let test_repo = TestRepo::init(use_git);
|
|
let repo = &test_repo.repo;
|
|
|
|
let op_heads_dir = 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(&settings, "transaction 1");
|
|
write_random_commit(tx1.mut_repo(), &settings);
|
|
let op_id1 = tx1.commit().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(&settings).unwrap();
|
|
let mut tx2 = repo.start_transaction(&settings, "transaction 2");
|
|
write_random_commit(tx2.mut_repo(), &settings);
|
|
let op_id2 = tx2.commit().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(&settings).unwrap();
|
|
assert_eq!(list_dir(&op_heads_dir), vec![op_id2.hex()]);
|
|
}
|
|
|
|
#[test_case(false ; "local backend")]
|
|
#[test_case(true ; "git backend")]
|
|
fn test_concurrent_operations(use_git: bool) {
|
|
// Test that consecutive operations result in multiple op-heads on disk until
|
|
// the repo has been reloaded (which currently happens right away).
|
|
let settings = testutils::user_settings();
|
|
let test_repo = TestRepo::init(use_git);
|
|
let repo = &test_repo.repo;
|
|
|
|
let op_heads_dir = 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(&settings, "transaction 1");
|
|
write_random_commit(tx1.mut_repo(), &settings);
|
|
let op_id1 = tx1.commit().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(&settings, "transaction 2");
|
|
write_random_commit(tx2.mut_repo(), &settings);
|
|
let op_id2 = tx2.commit().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(&settings).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_case(false ; "local backend")]
|
|
#[test_case(true ; "git backend")]
|
|
fn test_isolation(use_git: bool) {
|
|
// Test that two concurrent transactions don't see each other's changes.
|
|
let settings = testutils::user_settings();
|
|
let test_repo = TestRepo::init(use_git);
|
|
let repo = &test_repo.repo;
|
|
|
|
let mut tx = repo.start_transaction(&settings, "test");
|
|
let initial = create_random_commit(tx.mut_repo(), &settings)
|
|
.set_parents(vec![repo.store().root_commit_id().clone()])
|
|
.write()
|
|
.unwrap();
|
|
let repo = tx.commit();
|
|
|
|
let mut tx1 = repo.start_transaction(&settings, "transaction 1");
|
|
let mut_repo1 = tx1.mut_repo();
|
|
let mut tx2 = repo.start_transaction(&settings, "transaction 2");
|
|
let mut_repo2 = tx2.mut_repo();
|
|
|
|
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(&settings, &initial)
|
|
.set_description("rewrite1")
|
|
.write()
|
|
.unwrap();
|
|
mut_repo1.rebase_descendants(&settings).unwrap();
|
|
let rewrite2 = mut_repo2
|
|
.rewrite_commit(&settings, &initial)
|
|
.set_description("rewrite2")
|
|
.write()
|
|
.unwrap();
|
|
mut_repo2.rebase_descendants(&settings).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();
|
|
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();
|
|
assert_heads(repo.as_ref(), vec![initial.id()]);
|
|
// After reload, the base repo sees both rewrites.
|
|
let repo = repo.reload_at_head(&settings).unwrap();
|
|
assert_heads(repo.as_ref(), vec![rewrite1.id(), rewrite2.id()]);
|
|
}
|