diff --git a/Cargo.toml b/Cargo.toml index 11b3361..f703b9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,3 +96,4 @@ tokio = { version = "1.37", features = ["rt", "macros"] } assert2 = "0.3" pretty_assertions = "1.4" rand = "0.8" +mockall = "0.12" diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index f8eb9da..a1023fb 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -30,7 +30,7 @@ enum Server { async fn main() { let fs = fs::new(PathBuf::default()); let net = Network::new_real(); - let repo = git_next_git::repository::new(); + let repo = git_next_git::repository::real(); let commands = Commands::parse(); match commands.command { diff --git a/crates/git/Cargo.toml b/crates/git/Cargo.toml index 59345cb..4da1fea 100644 --- a/crates/git/Cargo.toml +++ b/crates/git/Cargo.toml @@ -51,12 +51,15 @@ actix = { workspace = true } # actix-rt = { workspace = true } # tokio = { workspace = true } +mockall = { workspace = true } + [dev-dependencies] # Testing assert2 = { workspace = true } rand = { workspace = true } pretty_assertions = { workspace = true } + [lints.clippy] nursery = { level = "warn", priority = -1 } # pedantic = "warn" diff --git a/crates/git/src/push.rs b/crates/git/src/push.rs index 7c4161d..9e3d987 100644 --- a/crates/git/src/push.rs +++ b/crates/git/src/push.rs @@ -47,7 +47,7 @@ pub enum Error { } pub fn reset( - repository: &git::OpenRepository, + repository: &dyn git::repository::OpenRepositoryLike, repo_details: &git::RepoDetails, branch_name: &config::BranchName, to_commit: &git::GitRef, diff --git a/crates/git/src/repository/mock.rs b/crates/git/src/repository/fake.rs similarity index 81% rename from crates/git/src/repository/mock.rs rename to crates/git/src/repository/fake.rs index f50008d..9f824a3 100644 --- a/crates/git/src/repository/mock.rs +++ b/crates/git/src/repository/fake.rs @@ -10,10 +10,10 @@ use crate as git; use git_next_config as config; #[derive(Debug, Default, Clone)] -pub struct MockRepository { - open_repos: Arc>>, +pub struct FakeRepository { + open_repos: Arc>>, } -impl MockRepository { +impl FakeRepository { pub fn new() -> Self { Self { open_repos: Default::default(), @@ -23,8 +23,8 @@ impl MockRepository { pub fn given_can_be_opened( &mut self, gitdir: &config::GitDir, - ) -> git::repository::MockOpenRepository { - let open_repo = git::repository::MockOpenRepository::new(); + ) -> git::repository::FakeOpenRepository { + let open_repo = git::repository::FakeOpenRepository::new(); #[allow(clippy::unwrap_used)] self.open_repos .lock() @@ -34,20 +34,20 @@ impl MockRepository { } pub fn seal(self) -> (git::Repository, Self) { - (git::Repository::Mock(self.clone()), self) + (git::Repository::Fake(self.clone()), self) } pub fn unseal(self, _repository: git::Repository) -> Self { // drop repository to allow same mutable access to mock repository self } - pub fn get(&self, gitdir: &config::GitDir) -> Option { + pub fn get(&self, gitdir: &config::GitDir) -> Option { self.open_repos .lock() .map(|or| or.get(gitdir).cloned()) .unwrap_or(None) } } -impl git::repository::RepositoryLike for MockRepository { +impl git::repository::RepositoryLike for FakeRepository { fn open( &self, gitdir: &config::GitDir, @@ -55,7 +55,7 @@ impl git::repository::RepositoryLike for MockRepository { #[allow(clippy::unwrap_used)] self.open_repos .lock() - .map_err(|_| crate::repository::Error::MockLock) + .map_err(|_| crate::repository::Error::FakeLock) .map(|or| or.get(gitdir).cloned()) .transpose() .unwrap_or_else(|| Err(crate::repository::Error::InvalidGitDir(gitdir.clone()))) diff --git a/crates/git/src/repository/mod.rs b/crates/git/src/repository/mod.rs index 14e1b1a..a468e4f 100644 --- a/crates/git/src/repository/mod.rs +++ b/crates/git/src/repository/mod.rs @@ -1,12 +1,16 @@ // #[cfg(test)] -mod mock; -#[cfg(test)] -pub use mock::MockRepository; -#[cfg(test)] -pub use open::MockOpenRepository; +mod fake; +use std::sync::{atomic::AtomicBool, Arc, Mutex}; -mod open; +use derive_more::Deref as _; + +#[cfg(test)] +pub use fake::FakeRepository; +#[cfg(test)] +pub use open::FakeOpenRepository; + +pub mod open; mod real; mod test; @@ -26,7 +30,6 @@ pub use real::RealRepository; use tracing::info; use crate::repository::test::TestRepository; - use crate::validation::repo::validate_repo; use super::RepoDetails; @@ -36,17 +39,18 @@ use super::RepoDetails; pub enum Repository { Real, #[cfg(test)] - Mock(MockRepository), + Fake(FakeRepository), Test(TestRepository), } +#[deprecated(note = "use git::repository::real()")] pub const fn new() -> Repository { Repository::Real } #[cfg(test)] -pub fn mock() -> MockRepository { - MockRepository::new() +pub fn fake() -> FakeRepository { + FakeRepository::new() } pub const fn test(fs: kxio::fs::FileSystem) -> TestRepository { @@ -62,11 +66,11 @@ pub const fn test(fs: kxio::fs::FileSystem) -> TestRepository { #[tracing::instrument(skip_all)] #[cfg(not(tarpaulin_include))] // requires network access to either clone new and/or fetch. pub fn open( - repository: &Repository, + repository: &dyn RepositoryFactory, repo_details: &RepoDetails, gitdir: config::GitDir, -) -> Result { - let repository = if !gitdir.exists() { +) -> Result> { + let open_repository = if !gitdir.exists() { info!("Local copy not found - cloning..."); repository.git_clone(repo_details)? } else { @@ -74,8 +78,50 @@ pub fn open( repository.open(&gitdir)? }; info!("Validating..."); - validate_repo(&repository, repo_details).map_err(|e| Error::Validation(e.to_string()))?; - Ok(repository) + validate_repo(&*open_repository, repo_details).map_err(|e| Error::Validation(e.to_string()))?; + Ok(open_repository) +} + +#[mockall::automock] +pub trait RepositoryFactory: std::fmt::Debug + Sync + Send { + fn duplicate(&self) -> Box; + fn open(&self, gitdir: &GitDir) -> Result>; + fn git_clone(&self, repo_details: &RepoDetails) -> Result>; +} + +pub fn real() -> Box { + Box::new(RealRepositoryFactory) +} + +pub fn mock() -> Box { + Box::new(MockRepositoryFactory::new()) +} + +#[derive(Debug, Clone)] +struct RealRepositoryFactory; +impl RepositoryFactory for RealRepositoryFactory { + fn open(&self, gitdir: &GitDir) -> Result> { + let gix_repo = gix::ThreadSafeRepository::open(gitdir.to_path_buf())?.to_thread_local(); + let repo = RealOpenRepository::new(Arc::new(Mutex::new(gix_repo))); + Ok(Box::new(repo)) + } + + fn git_clone(&self, repo_details: &RepoDetails) -> Result> { + tracing::info!("creating"); + use secrecy::ExposeSecret; + let (gix_repo, _outcome) = gix::prepare_clone_bare( + repo_details.origin().expose_secret().as_str(), + repo_details.gitdir.deref(), + )? + .fetch_only(gix::progress::Discard, &AtomicBool::new(false))?; + tracing::info!("created"); + let repo = RealOpenRepository::new(Arc::new(Mutex::new(gix_repo))); + Ok(Box::new(repo)) + } + + fn duplicate(&self) -> Box { + Box::new(self.clone()) + } } pub trait RepositoryLike { @@ -91,7 +137,7 @@ impl std::ops::Deref for Repository { Self::Test(test_repository) => test_repository, #[cfg(test)] - Self::Mock(mock_repository) => mock_repository, + Self::Fake(mock_repository) => mock_repository, } } } @@ -140,8 +186,8 @@ pub enum Error { #[error("git fetch: {0}")] Fetch(String), - #[error("mock lock")] - MockLock, + #[error("fake repository lock")] + FakeLock, } mod gix_errors { diff --git a/crates/git/src/repository/open/mod.rs b/crates/git/src/repository/open/mod.rs index 0082fe0..d803379 100644 --- a/crates/git/src/repository/open/mod.rs +++ b/crates/git/src/repository/open/mod.rs @@ -8,7 +8,7 @@ pub mod oreal; pub mod otest; #[cfg(test)] -pub mod omock; +pub mod ofake; use std::{ path::Path, @@ -19,7 +19,7 @@ use crate as git; use git::repository::Direction; use git_next_config as config; #[cfg(test)] -pub use omock::MockOpenRepository; +pub use ofake::FakeOpenRepository; pub use oreal::RealOpenRepository; pub use otest::TestOpenRepository; @@ -45,7 +45,7 @@ pub enum OpenRepository { /// variant is ready for use, tests should be converted to using /// that instead. #[cfg(test)] - Mock(git::repository::MockOpenRepository), // TODO: (#38) contain a mock model of a repo + Mock(git::repository::FakeOpenRepository), // TODO: (#38) contain a mock model of a repo } pub fn real(gix_repo: gix::Repository) -> OpenRepository { @@ -74,7 +74,9 @@ pub fn test_bare( OpenRepository::Test(TestOpenRepository::new_bare(gitdir, fs, on_fetch, on_push)) } -pub trait OpenRepositoryLike { +#[mockall::automock] +pub trait OpenRepositoryLike: std::fmt::Debug + Sync { + fn duplicate(&self) -> Box; fn remote_branches(&self) -> git::push::Result>; fn find_default_remote(&self, direction: Direction) -> Option; fn fetch(&self) -> Result<(), git::fetch::Error>; @@ -102,6 +104,11 @@ pub trait OpenRepositoryLike { file_name: &Path, ) -> git::file::Result; } + +pub fn mock() -> Box { + Box::new(MockOpenRepositoryLike::new()) +} + impl std::ops::Deref for OpenRepository { type Target = dyn OpenRepositoryLike; diff --git a/crates/git/src/repository/open/omock.rs b/crates/git/src/repository/open/ofake.rs similarity index 90% rename from crates/git/src/repository/open/omock.rs rename to crates/git/src/repository/open/ofake.rs index be1968e..44aefc2 100644 --- a/crates/git/src/repository/open/omock.rs +++ b/crates/git/src/repository/open/ofake.rs @@ -1,5 +1,5 @@ // -use crate as git; +use crate::{self as git, repository::OpenRepositoryLike}; use git_next_config as config; use std::{ @@ -8,12 +8,12 @@ use std::{ }; #[derive(Clone, Debug, Default)] -pub struct MockOpenRepository { +pub struct FakeOpenRepository { default_push_remote: Arc>>, default_fetch_remote: Arc>>, operations: Arc>>, } -impl MockOpenRepository { +impl FakeOpenRepository { pub fn new() -> Self { Self::default() } @@ -50,13 +50,13 @@ impl MockOpenRepository { .unwrap_or_default() } } -impl From for git::OpenRepository { - fn from(value: MockOpenRepository) -> Self { +impl From for git::OpenRepository { + fn from(value: FakeOpenRepository) -> Self { Self::Mock(value) } } #[allow(clippy::unwrap_used)] -impl git::repository::OpenRepositoryLike for MockOpenRepository { +impl git::repository::OpenRepositoryLike for FakeOpenRepository { fn remote_branches(&self) -> git::push::Result> { todo!("MockOpenRepository::remote_branched") } @@ -118,4 +118,8 @@ impl git::repository::OpenRepositoryLike for MockOpenRepository { ) -> git::file::Result { todo!("MockOpenRepository::read_file") } + + fn duplicate(&self) -> Box { + Box::new(self.clone()) + } } diff --git a/crates/git/src/repository/open/oreal.rs b/crates/git/src/repository/open/oreal.rs index 58426b8..486659c 100644 --- a/crates/git/src/repository/open/oreal.rs +++ b/crates/git/src/repository/open/oreal.rs @@ -1,5 +1,5 @@ // -use crate as git; +use crate::{self as git, repository::OpenRepositoryLike}; use config::BranchName; use derive_more::Constructor; use git_next_config as config; @@ -201,6 +201,10 @@ impl super::OpenRepositoryLike for RealOpenRepository { Ok(content) }) } + + fn duplicate(&self) -> Box { + Box::new(self.clone()) + } } fn as_gix_error(branch: BranchName) -> impl FnOnce(String) -> git::commit::log::Error { diff --git a/crates/git/src/repository/open/otest.rs b/crates/git/src/repository/open/otest.rs index 7720011..337dfc6 100644 --- a/crates/git/src/repository/open/otest.rs +++ b/crates/git/src/repository/open/otest.rs @@ -1,5 +1,5 @@ // -use crate as git; +use crate::{self as git, repository::OpenRepositoryLike}; use derive_more::{Constructor, Deref}; use git_next_config as config; @@ -132,6 +132,10 @@ impl git::repository::OpenRepositoryLike for TestOpenRepository { ) -> git::file::Result { self.real.read_file(branch_name, file_name) } + + fn duplicate(&self) -> Box { + Box::new(self.clone()) + } } impl TestOpenRepository { pub fn new( diff --git a/crates/git/src/repository/open/tests.rs b/crates/git/src/repository/open/tests.rs index 1afedd6..b9a331b 100644 --- a/crates/git/src/repository/open/tests.rs +++ b/crates/git/src/repository/open/tests.rs @@ -2,7 +2,6 @@ use crate as git; use crate::repository::RepositoryLike as _; use git::tests::given; -use git::tests::then; use git_next_config as config; use assert2::let_assert; @@ -50,6 +49,7 @@ mod server_repo_config { ) ); } + #[test] fn should_return_repo() { let repo_path = given::a_name(); @@ -64,6 +64,7 @@ mod server_repo_config { assert_eq!(src.repo(), config::RepoPath::new(repo_path)); } + #[test] fn should_return_branch() { let branch = given::a_name(); @@ -78,6 +79,7 @@ mod server_repo_config { assert_eq!(src.branch(), config::BranchName::new(branch)); } + #[test] fn should_return_gitdir() { let gitdir = given::a_name(); @@ -96,6 +98,7 @@ mod server_repo_config { ); } } + mod repo_config { use super::*; @@ -149,6 +152,7 @@ mod repo_config { assert_eq!(repo_config.source(), config::RepoConfigSource::Repo); } } + mod forge_config { use super::*; use std::collections::BTreeMap; @@ -198,6 +202,7 @@ mod forge_config { ] ); } + #[test] fn should_return_forge_type() { let forge_type = config::ForgeType::MockForge; @@ -209,6 +214,7 @@ mod forge_config { assert_eq!(fc.forge_type(), config::ForgeType::MockForge); } + #[test] fn should_return_hostname() { let forge_type = config::ForgeType::MockForge; @@ -220,6 +226,7 @@ mod forge_config { assert_eq!(fc.hostname(), config::Hostname::new(hostname)); } + #[test] fn should_return_user() { let forge_type = config::ForgeType::MockForge; @@ -231,6 +238,7 @@ mod forge_config { assert_eq!(fc.user(), config::User::new(user)); } + #[test] fn should_return_token() { let forge_type = config::ForgeType::MockForge; @@ -242,6 +250,7 @@ mod forge_config { assert_eq!(fc.token().expose_secret(), token.as_str()); } + #[test] fn should_return_repo() { let forge_type = config::ForgeType::MockForge; @@ -266,6 +275,7 @@ mod forge_config { None, None, ); + let mut repos = BTreeMap::new(); repos.insert(red_name.clone(), red.clone()); repos.insert(blue_name, blue); @@ -277,6 +287,27 @@ mod forge_config { } } +// mod remote_branches { +// use super::*; +// #[test] +// // assumes running in the git-next repo which should have main, next and dev as remote branches +// fn should_return_remote_branches() -> TestResult { +// let_assert!(Ok(fs) = kxio::fs::temp()); +// let gitdir: config::GitDir = fs.base().to_path_buf().into(); +// let test_repository = git::repository::test(fs.clone()); +// let_assert!(Ok(open_repository) = test_repository.open(&gitdir)); +// let repo_config = &given::a_repo_config(); +// let branches = repo_config.branches(); +// then::create_a_commit_on_branch(&fs, &gitdir, &branches.main())?; +// then::create_a_commit_on_branch(&fs, &gitdir, &branches.next())?; +// then::create_a_commit_on_branch(&fs, &gitdir, &branches.dev())?; +// let_assert!(Ok(remote_branches) = open_repository.remote_branches()); +// assert!(remote_branches.contains(&branches.main())); +// assert!(remote_branches.contains(&branches.next())); +// assert!(remote_branches.contains(&branches.dev())); +// Ok(()) +// } +// } mod find_default_remote { use super::*; @@ -287,7 +318,7 @@ mod find_default_remote { // uses the current repo let_assert!(Ok(cwd) = std::env::current_dir()); let gitdir = config::GitDir::from(cwd.join("../..")); // from ./crate/git directory to the project rook - let_assert!(Ok(repo) = crate::repository::new().open(&gitdir)); + let_assert!(Ok(repo) = crate::repository::real().open(&gitdir)); let_assert!(Some(remote) = repo.find_default_remote(crate::repository::Direction::Push)); assert_eq!( remote, @@ -304,37 +335,16 @@ mod fetch { use git_next_config::GitDir; #[test] - #[ignore] // requires authentication to the server + #[ignore] // requires authentication to the server - which the CI doesn't have fn should_fetch_from_repo() { // uses the current repo and fetches from the remote server let_assert!(Ok(cwd) = std::env::current_dir()); let gitdir = GitDir::from(cwd.join("../..")); - let_assert!(Ok(repo) = crate::repository::new().open(&gitdir)); + let_assert!(Ok(repo) = crate::repository::real().open(&gitdir)); let_assert!(Ok(_) = repo.fetch()); } } -mod remote_branches { - use super::*; - #[test] - // assumes running in the git-next repo which should have main, next and dev as remote branches - fn should_return_remote_branches() -> TestResult { - let_assert!(Ok(fs) = kxio::fs::temp()); - let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let test_repository = git::repository::test(fs.clone()); - let_assert!(Ok(open_repository) = test_repository.open(&gitdir)); - let repo_config = &given::a_repo_config(); - let branches = repo_config.branches(); - then::create_a_commit_on_branch(&fs, &gitdir, &branches.main())?; - then::create_a_commit_on_branch(&fs, &gitdir, &branches.next())?; - then::create_a_commit_on_branch(&fs, &gitdir, &branches.dev())?; - let_assert!(Ok(remote_branches) = open_repository.remote_branches()); - assert!(remote_branches.contains(&branches.main())); - assert!(remote_branches.contains(&branches.next())); - assert!(remote_branches.contains(&branches.dev())); - Ok(()) - } -} mod commit_log { use git::tests::given; @@ -407,6 +417,7 @@ mod commit_log { Ok(()) } } + mod read_file { use git::tests::given; diff --git a/crates/git/src/repository/tests.rs b/crates/git/src/repository/tests.rs index 337df18..695a27b 100644 --- a/crates/git/src/repository/tests.rs +++ b/crates/git/src/repository/tests.rs @@ -1,173 +1,100 @@ use crate as git; mod validate { - use crate::{validation::repo::validate_repo, GitRemote, RepoDetails}; + use crate::{tests::given, validation::repo::validate_repo}; use super::*; - use assert2::let_assert; - use git_next_config::{ForgeDetails, GitDir, Hostname, RepoPath}; + use git::repository::Direction; #[test] fn should_ok_a_valid_repo() { - let repo_details = RepoDetails::default() - .with_forge( - ForgeDetails::default().with_hostname(Hostname::new("localhost".to_string())), - ) - .with_repo_path(RepoPath::new("kemitix/test".to_string())); - let gitdir = GitDir::from("foo"); - let remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/test".to_string()), - ); + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs); + let repo_details_mock = repo_details.clone(); - let mut mock_repository = git::repository::mock(); - { - let mut mock_open_repo = mock_repository.given_can_be_opened(&gitdir); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Push, Some(remote.clone())); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Fetch, Some(remote)); - } - let (repository, _mock_repository) = mock_repository.seal(); - let_assert!(Ok(open_repository) = repository.open(&gitdir)); - let_assert!(Ok(_) = validate_repo(&open_repository, &repo_details)); + let mut open_repository = git::repository::open::mock(); + open_repository + .expect_find_default_remote() + .returning(move |_direction| Some(repo_details_mock.git_remote())); + + let result = validate_repo(&*open_repository, &repo_details); + println!("{result:?}"); + assert!(result.is_ok()); } #[test] fn should_fail_where_no_default_push_remote() { - let repo_details = RepoDetails::default() - .with_forge( - ForgeDetails::default().with_hostname(Hostname::new("localhost".to_string())), - ) - .with_repo_path(RepoPath::new("kemitix/test".to_string())); - let gitdir = GitDir::from("foo"); - let remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/test".to_string()), - ); + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs); + let repo_details_mock = repo_details.clone(); - let mut mock_repository = git::repository::mock(); - { - let mut mock_open_repo = mock_repository.given_can_be_opened(&gitdir); - mock_open_repo.given_has_default_remote(git::repository::Direction::Push, None); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Fetch, Some(remote)); - } - let (repository, _mock_repository) = mock_repository.seal(); - let_assert!(Ok(open_repository) = repository.open(&gitdir)); - let_assert!(Err(_) = validate_repo(&open_repository, &repo_details)); + let mut open_repository = git::repository::open::mock(); + open_repository + .expect_find_default_remote() + .returning(move |direction| match direction { + Direction::Push => None, + Direction::Fetch => Some(repo_details_mock.git_remote()), + }); + + let result = validate_repo(&*open_repository, &repo_details); + println!("{result:?}"); + assert!(result.is_err()); } + #[test] fn should_fail_where_no_default_fetch_remote() { - let repo_details = RepoDetails::default() - .with_forge( - ForgeDetails::default().with_hostname(Hostname::new("localhost".to_string())), - ) - .with_repo_path(RepoPath::new("kemitix/test".to_string())); - let gitdir = GitDir::from("foo"); - let remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/test".to_string()), - ); + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs); + let repo_details_mock = repo_details.clone(); - let mut mock_repository = git::repository::mock(); - { - let mut mock_open_repo = mock_repository.given_can_be_opened(&gitdir); - mock_open_repo.given_has_default_remote(git::repository::Direction::Push, None); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Fetch, Some(remote)); - } - let (repository, _mock_repository) = mock_repository.seal(); - let_assert!(Ok(open_repository) = repository.open(&gitdir)); - let_assert!(Err(_) = validate_repo(&open_repository, &repo_details)); + let mut open_repository = git::repository::open::mock(); + open_repository + .expect_find_default_remote() + .returning(move |direction| match direction { + Direction::Push => Some(repo_details_mock.git_remote()), + Direction::Fetch => None, + }); + + let result = validate_repo(&*open_repository, &repo_details); + println!("{result:?}"); + assert!(result.is_err()); } + #[test] fn should_fail_where_invalid_default_push_remote() { - let repo_details = RepoDetails::default() - .with_forge( - ForgeDetails::default().with_hostname(Hostname::new("localhost".to_string())), - ) - .with_repo_path(RepoPath::new("kemitix/test".to_string())); - let gitdir = GitDir::from("foo"); - let remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/test".to_string()), - ); - let other_remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/other".to_string()), - ); + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs); + let repo_details_mock = repo_details.clone(); - let mut mock_repository = git::repository::mock(); - { - let mut mock_open_repo = mock_repository.given_can_be_opened(&gitdir); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Push, Some(other_remote)); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Fetch, Some(remote)); - } - let (repository, _mock_repository) = mock_repository.seal(); - let_assert!(Ok(open_repository) = repository.open(&gitdir)); - let_assert!(Err(_) = validate_repo(&open_repository, &repo_details)); + let mut open_repository = git::repository::open::mock(); + open_repository + .expect_find_default_remote() + .returning(move |direction| match direction { + Direction::Push => Some(given::a_git_remote()), + Direction::Fetch => Some(repo_details_mock.git_remote()), + }); + + let result = validate_repo(&*open_repository, &repo_details); + println!("{result:?}"); + assert!(result.is_err()); } + #[test] fn should_fail_where_invalid_default_fetch_remote() { - let repo_details = RepoDetails::default() - .with_forge( - ForgeDetails::default().with_hostname(Hostname::new("localhost".to_string())), - ) - .with_repo_path(RepoPath::new("kemitix/test".to_string())); - let gitdir = GitDir::from("foo"); - let remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/test".to_string()), - ); - let other_remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/other".to_string()), - ); + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs); + let repo_details_mock = repo_details.clone(); - let mut mock_repository = git::repository::mock(); - { - let mut mock_open_repo = mock_repository.given_can_be_opened(&gitdir); - mock_open_repo.given_has_default_remote(git::repository::Direction::Push, Some(remote)); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Fetch, Some(other_remote)); - } - let (repository, _mock_repository) = mock_repository.seal(); - let_assert!(Ok(open_repository) = repository.open(&gitdir)); - let_assert!(Err(_) = validate_repo(&open_repository, &repo_details)); - } -} - -mod git_clone { - use super::*; - use assert2::let_assert; - use git_next_config::{ForgeDetails, GitDir, Hostname, RepoPath}; - - use crate::{GitRemote, RepoDetails}; - - #[test] - #[ignore] // slow test ~1.5 seconds - fn should_clone_repo() { - let_assert!(Ok(fs) = kxio::fs::temp()); - let r = crate::repository::new(); - let repo_details = RepoDetails::default() - .with_forge( - ForgeDetails::default().with_hostname(Hostname::new("git.kemitix.net".to_string())), - ) - .with_gitdir(GitDir::new(fs.base())) - .with_repo_path(RepoPath::new("kemitix/git-next".to_string())); - let_assert!(Ok(open_repo) = r.git_clone(&repo_details)); - let_assert!( - Some(remote) = open_repo.find_default_remote(git::repository::Direction::Fetch) - ); - assert_eq!( - remote, - GitRemote::new( - Hostname::new("git.kemitix.net"), - RepoPath::new("kemitix/git-next".to_string()) - ) - ); + let mut open_repository = git::repository::open::mock(); + open_repository + .expect_find_default_remote() + .returning(move |direction| match direction { + Direction::Push => Some(repo_details_mock.git_remote()), + Direction::Fetch => Some(given::a_git_remote()), + }); + + let result = validate_repo(&*open_repository, &repo_details); + println!("{result:?}"); + assert!(result.is_err()); } } diff --git a/crates/git/src/tests.rs b/crates/git/src/tests.rs index 40dbb13..d0c33df 100644 --- a/crates/git/src/tests.rs +++ b/crates/git/src/tests.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use crate as git; use git_next_config as config; @@ -102,37 +104,38 @@ mod push { mod reset { use super::*; - use crate::{tests::given, OpenRepository}; + use crate::tests::given; use assert2::let_assert; #[test] fn should_perform_a_fetch_then_push() { + let mut open_repository = git::repository::open::mock(); + let mut seq = mockall::Sequence::new(); + open_repository + .expect_fetch() + .times(1) + .in_sequence(&mut seq) + .returning(|| Ok(())); + open_repository + .expect_push() + .times(1) + .in_sequence(&mut seq) + .returning(|_repo_details, _branch_name, _gitref, _force| Ok(())); + let fs = given::a_filesystem(); - let (mock_open_repository, gitdir, mock_repository) = given::an_open_repository(&fs); - let open_repository: OpenRepository = mock_open_repository.into(); let repo_details = given::repo_details(&fs); let branch_name = &repo_details.branch; let commit = given::a_commit(); let gitref = GitRef::from(commit); let_assert!( Ok(_) = git::push::reset( - &open_repository, + &*open_repository, &repo_details, branch_name, &gitref, &git::push::Force::No ) ); - let_assert!(Some(mock_open_repository) = mock_repository.get(&gitdir)); - let operations = mock_open_repository.operations(); - let forge_alias = repo_details.forge.forge_alias(); - let repo_alias = &repo_details.repo_alias; - let to_commit = gitref; - let force = "fast-forward"; - assert_eq!( - operations, - vec![format!("fetch"), format!("push fa:{forge_alias} ra:{repo_alias} bn:{branch_name} tc:{to_commit} f:{force}")] - ); } } } @@ -215,7 +218,7 @@ pub mod given { // use crate::{ self as git, - repository::{MockOpenRepository, MockRepository}, + repository::{FakeOpenRepository, FakeRepository}, tests::given, }; use config::{ @@ -348,12 +351,19 @@ pub mod given { pub fn an_open_repository( fs: &kxio::fs::FileSystem, - ) -> (MockOpenRepository, GitDir, MockRepository) { - let mut mock = git::repository::mock(); + ) -> (FakeOpenRepository, GitDir, FakeRepository) { + let mut mock = git::repository::fake(); let gitdir = a_git_dir(fs); let or = mock.given_can_be_opened(&gitdir); (or, gitdir, mock) } + + pub fn a_git_remote() -> git::GitRemote { + git::GitRemote::new( + config::Hostname::new(given::a_name()), + config::RepoPath::new(given::a_name()), + ) + } } pub mod then { use std::path::{Path, PathBuf}; diff --git a/crates/git/src/validation/positions.rs b/crates/git/src/validation/positions.rs index 61ee563..f8acd64 100644 --- a/crates/git/src/validation/positions.rs +++ b/crates/git/src/validation/positions.rs @@ -16,7 +16,7 @@ pub struct Positions { #[allow(clippy::cognitive_complexity)] // TODO: (#83) reduce complexity pub fn validate_positions( - repository: &git::OpenRepository, + open_repository: &dyn git::repository::OpenRepositoryLike, repo_details: &git::RepoDetails, repo_config: config::RepoConfig, ) -> Result { @@ -24,9 +24,9 @@ pub fn validate_positions( let next_branch = repo_config.branches().next(); let dev_branch = repo_config.branches().dev(); // Collect Commit Histories for `main`, `next` and `dev` branches - repository.fetch()?; + open_repository.fetch()?; let commit_histories = - get_commit_histories(repository, &repo_config).map_err(Error::CommitLog)?; + get_commit_histories(open_repository, &repo_config).map_err(Error::CommitLog)?; // branch tips let main = commit_histories .main @@ -55,12 +55,12 @@ pub fn validate_positions( // verify that next is on main or at most one commit on top of main, else reset it back to main if is_not_based_on(&commit_histories.next[0..=1], &main) { info!("Main not on same commit as next, or it's parent - resetting next to main",); - return reset_next_to_main(repository, repo_details, &main, &next, &next_branch); + return reset_next_to_main(open_repository, repo_details, &main, &next, &next_branch); } // verify that next is an ancestor of dev, else reset it back to main if is_not_based_on(&commit_histories.dev, &next) { info!("Next is not an ancestor of dev - resetting next to main"); - return reset_next_to_main(repository, repo_details, &main, &next, &next_branch); + return reset_next_to_main(open_repository, repo_details, &main, &next, &next_branch); } Ok(git::validation::positions::Positions { @@ -72,7 +72,7 @@ pub fn validate_positions( } fn reset_next_to_main( - repository: &crate::OpenRepository, + repository: &dyn crate::repository::OpenRepositoryLike, repo_details: &crate::RepoDetails, main: &crate::Commit, next: &crate::Commit, @@ -100,7 +100,7 @@ fn is_not_based_on(commits: &[crate::commit::Commit], needle: &crate::Commit) -> } fn get_commit_histories( - repository: &git::repository::OpenRepository, + repository: &dyn git::repository::OpenRepositoryLike, repo_config: &config::RepoConfig, ) -> git::commit::log::Result { let main = (repository.commit_log(&repo_config.branches().main(), &[]))?; diff --git a/crates/git/src/validation/repo.rs b/crates/git/src/validation/repo.rs index 8052838..46c9560 100644 --- a/crates/git/src/validation/repo.rs +++ b/crates/git/src/validation/repo.rs @@ -4,13 +4,13 @@ use crate as git; #[tracing::instrument(skip_all)] pub fn validate_repo( - repository: &git::OpenRepository, + open_repository: &dyn git::repository::OpenRepositoryLike, repo_details: &git::RepoDetails, ) -> Result<()> { - let push_remote = repository + let push_remote = open_repository .find_default_remote(git::repository::Direction::Push) .ok_or_else(|| Error::NoDefaultPushRemote)?; - let fetch_remote = repository + let fetch_remote = open_repository .find_default_remote(git::repository::Direction::Fetch) .ok_or_else(|| Error::NoDefaultFetchRemote)?; let git_remote = repo_details.git_remote(); diff --git a/crates/git/src/validation/tests.rs b/crates/git/src/validation/tests.rs index 549e82d..37d8c9a 100644 --- a/crates/git/src/validation/tests.rs +++ b/crates/git/src/validation/tests.rs @@ -1,13 +1,16 @@ // use crate as git; +use git::repository::RepositoryFactory as _; +use git::repository::RepositoryLike as _; use git::tests::given; use git_next_config as config; use assert2::let_assert; mod repos { - use crate::repository::RepositoryLike as _; + + use crate::repository::Direction; use super::*; @@ -15,23 +18,34 @@ mod repos { fn where_repo_has_no_push_remote() { let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let test_repository = git::repository::test(fs.clone()); - // default has no push or fetch remotes + let mut mock_open_repository = git::repository::open::mock(); + mock_open_repository + .expect_find_default_remote() + .with(mockall::predicate::eq(Direction::Push)) + .return_once(|_| None); + let mut repository_factory = git::repository::mock(); + repository_factory + .expect_open() + .return_once(move |_| Ok(mock_open_repository)); let repo_details = given::repo_details(&fs); - let_assert!(Ok(repository) = test_repository.open(&gitdir), "open repo"); + let_assert!( + Ok(open_repository) = repository_factory.open(&gitdir), + "open repo" + ); - let_assert!(Err(err) = git::validation::repo::validate_repo(&repository, &repo_details)); + let result = git::validation::repo::validate_repo(&*open_repository, &repo_details); + print!("{result:?}"); + let_assert!(Err(err) = result); assert!(matches!( err, git::validation::repo::Error::NoDefaultPushRemote )); } } + mod positions { use super::*; - use git::repository::RepositoryLike as _; - mod validate_positions { use git::validation::positions::validate_positions; @@ -42,23 +56,26 @@ mod positions { #[test] fn where_fetch_fails_should_error() { - let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); + let fs = given::a_filesystem(); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); - test_repository.on_fetch(git::repository::OnFetch::new( - given::repo_branches(), - gitdir.clone(), - fs.clone(), - |_, _, _| git::fetch::Result::Err(git::fetch::Error::TestFailureExpected), - )); - let_assert!(Ok(repository) = test_repository.open(&gitdir), "open repo"); - let repo_details = given::repo_details(&fs).with_gitdir(gitdir); + let mut mock_open_repository = git::repository::open::mock(); + mock_open_repository + .expect_fetch() + .return_once(|| Err(git::fetch::Error::TestFailureExpected)); + let mut repository_factory = git::repository::mock(); + repository_factory + .expect_open() + .return_once(move |_| Ok(mock_open_repository)); + let_assert!( + Ok(repository) = repository_factory.open(&gitdir), + "open repo" + ); + let repo_details = given::repo_details(&fs); //.with_gitdir(gitdir); let repo_config = given::a_repo_config(); - let_assert!( - Err(err) = validate_positions(&repository, &repo_details, repo_config), - "validate" - ); + let result = validate_positions(&*repository, &repo_details, repo_config); + println!("{result:?}"); + let_assert!(Err(err) = result, "validate"); assert!(matches!( err, @@ -68,123 +85,137 @@ mod positions { #[test] fn where_main_branch_is_missing_or_commit_log_is_empty_should_error() { - let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); + let fs = given::a_filesystem(); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); - test_repository.on_fetch(git::repository::OnFetch::new( - given::repo_branches(), - gitdir.clone(), - fs.clone(), - |_, _, _| git::fetch::Result::Ok(()), - )); - // test repo is a new bare repo with no commits - let_assert!(Ok(repository) = test_repository.open(&gitdir), "open repo"); - let repo_details = given::repo_details(&fs).with_gitdir(gitdir); let repo_config = given::a_repo_config(); - + let main_branch = repo_config.branches().main(); + let mut mock_open_repository = git::repository::open::mock(); + mock_open_repository.expect_fetch().return_once(|| Ok(())); + mock_open_repository + .expect_commit_log() + .returning(move |branch_name, _| { + if branch_name == &main_branch { + Err(git::commit::log::Error::Gix { + branch: branch_name.clone(), + error: "foo".to_string(), + }) + } else { + Ok(vec![]) + } + }); + let mut repository_factory = git::repository::mock(); + repository_factory + .expect_open() + .return_once(move |_| Ok(mock_open_repository)); let_assert!( - Err(err) = validate_positions(&repository, &repo_details, repo_config.clone()), - "validate" - ); - let branch_name = repo_config.branches().main(); - let error_message = format!( - r#"The ref partially named "remotes/origin/{branch_name}" could not be found"# + Ok(open_repository) = repository_factory.open(&gitdir), + "open repo" ); + let repo_details = given::repo_details(&fs); + let main_branch = repo_config.branches().main(); + + let result = validate_positions(&*open_repository, &repo_details, repo_config); + println!("{result:?}"); + assert!(matches!( - err, - git::validation::positions::Error::CommitLog(git::commit::log::Error::Gix { + result, + Err(git::validation::positions::Error::CommitLog(git::commit::log::Error::Gix { branch, error - }) if branch == branch_name && error == error_message + })) if branch == main_branch && error == "foo" )); } #[test] fn where_next_branch_is_missing_or_commit_log_is_empty_should_error() { - //given - let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); + let fs = given::a_filesystem(); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); let repo_config = given::a_repo_config(); - test_repository.on_fetch(git::repository::OnFetch::new( - repo_config.branches().clone(), - gitdir.clone(), - fs.clone(), - |branches, gitdir, fs| { - // add a commit to main - then::create_a_commit_on_branch(fs, gitdir, &branches.main())?; - git::fetch::Result::Ok(()) - }, - )); - let_assert!(Ok(repository) = test_repository.open(&gitdir), "open repo"); - let repo_details = given::repo_details(&fs).with_gitdir(gitdir); - - //when + let next_branch = repo_config.branches().next(); + let mut mock_open_repository = git::repository::open::mock(); + mock_open_repository.expect_fetch().return_once(|| Ok(())); + mock_open_repository + .expect_commit_log() + .returning(move |branch_name, _| { + if branch_name == &next_branch { + Err(git::commit::log::Error::Gix { + branch: branch_name.clone(), + error: "foo".to_string(), + }) + } else { + Ok(vec![given::a_commit()]) + } + }); + let mut repository_factory = git::repository::mock(); + repository_factory + .expect_open() + .return_once(move |_| Ok(mock_open_repository)); let_assert!( - Err(err) = validate_positions(&repository, &repo_details, repo_config.clone()), - "validate" + Ok(open_repository) = repository_factory.open(&gitdir), + "open repo" ); + let repo_details = given::repo_details(&fs); + let next_branch = repo_config.branches().next(); + + let result = validate_positions(&*open_repository, &repo_details, repo_config); + println!("{result:?}"); - //then - let branch_name = repo_config.branches().next(); - let error_message = format!( - r#"The ref partially named "remotes/origin/{branch_name}" could not be found"# - ); assert!(matches!( - err, - git::validation::positions::Error::CommitLog(git::commit::log::Error::Gix { + result, + Err(git::validation::positions::Error::CommitLog(git::commit::log::Error::Gix { branch, error - }) if branch == branch_name && error == error_message + })) if branch == next_branch && error == "foo" )); } #[test] fn where_dev_branch_is_missing_or_commit_log_is_empty_should_error() { - //given - let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); + let fs = given::a_filesystem(); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); let repo_config = given::a_repo_config(); - test_repository.on_fetch(git::repository::OnFetch::new( - repo_config.branches().clone(), - gitdir.clone(), - fs.clone(), - |branches, gitdir, fs| { - // add a commit to main - then::create_a_commit_on_branch(fs, gitdir, &branches.main())?; - // add a commit to next - then::create_a_commit_on_branch(fs, gitdir, &branches.next())?; - git::fetch::Result::Ok(()) - }, - )); - let_assert!(Ok(repository) = test_repository.open(&gitdir), "open repo"); - let repo_details = given::repo_details(&fs).with_gitdir(gitdir); - - //when + let dev_branch = repo_config.branches().dev(); + let mut mock_open_repository = git::repository::open::mock(); + mock_open_repository.expect_fetch().return_once(|| Ok(())); + mock_open_repository + .expect_commit_log() + .returning(move |branch_name, _| { + if branch_name == &dev_branch { + Err(git::commit::log::Error::Gix { + branch: branch_name.clone(), + error: "foo".to_string(), + }) + } else { + Ok(vec![given::a_commit()]) + } + }); + let mut repository_factory = git::repository::mock(); + repository_factory + .expect_open() + .return_once(move |_| Ok(mock_open_repository)); let_assert!( - Err(err) = validate_positions(&repository, &repo_details, repo_config.clone()), - "validate" + Ok(open_repository) = repository_factory.open(&gitdir), + "open repo" ); + let repo_details = given::repo_details(&fs); + let dev_branch = repo_config.branches().dev(); + + let result = validate_positions(&*open_repository, &repo_details, repo_config); + println!("{result:?}"); - //then - let branch_name = repo_config.branches().dev(); - let error_message = format!( - r#"The ref partially named "remotes/origin/{branch_name}" could not be found"# - ); assert!(matches!( - err, - git::validation::positions::Error::CommitLog(git::commit::log::Error::Gix { + result, + Err(git::validation::positions::Error::CommitLog(git::commit::log::Error::Gix { branch, error - }) if branch == branch_name && error == error_message + })) if branch == dev_branch && error == "foo" )); } #[test] fn where_dev_branch_is_not_based_on_main_should_error() { //given - let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); + let fs = given::a_filesystem(); let gitdir: config::GitDir = fs.base().to_path_buf().into(); let mut test_repository = git::repository::test(fs.clone()); let repo_config = given::a_repo_config(); @@ -211,12 +242,16 @@ mod positions { git::fetch::Result::Ok(()) }, )); - let_assert!(Ok(repository) = test_repository.open(&gitdir), "open repo"); + let_assert!( + Ok(open_repository) = test_repository.open(&gitdir), + "open repo" + ); let repo_details = given::repo_details(&fs).with_gitdir(gitdir); //when let_assert!( - Err(err) = validate_positions(&repository, &repo_details, repo_config.clone()), + Err(err) = + validate_positions(&*open_repository, &repo_details, repo_config.clone()), "validate" ); @@ -294,12 +329,16 @@ mod positions { git::push::Result::Ok(()) }, )); - let_assert!(Ok(repository) = test_repository.open(&gitdir), "open repo"); + let_assert!( + Ok(open_repository) = test_repository.open(&gitdir), + "open repo" + ); let repo_details = given::repo_details(&fs).with_gitdir(gitdir); //when let_assert!( - Err(err) = validate_positions(&repository, &repo_details, repo_config.clone()), + Err(err) = + validate_positions(&*open_repository, &repo_details, repo_config.clone()), "validate" ); @@ -361,12 +400,16 @@ mod positions { git::push::Result::Err(git::push::Error::Lock) }, )); - let_assert!(Ok(repository) = test_repository.open(&gitdir), "open repo"); + let_assert!( + Ok(open_repository) = test_repository.open(&gitdir), + "open repo" + ); let repo_details = given::repo_details(&fs).with_gitdir(gitdir.clone()); //when let_assert!( - Err(err) = validate_positions(&repository, &repo_details, repo_config.clone()), + Err(err) = + validate_positions(&*open_repository, &repo_details, repo_config.clone()), "validate" ); @@ -447,12 +490,16 @@ mod positions { git::push::Result::Ok(()) }, )); - let_assert!(Ok(repository) = test_repository.open(&gitdir), "open repo"); + let_assert!( + Ok(open_repository) = test_repository.open(&gitdir), + "open repo" + ); let repo_details = given::repo_details(&fs).with_gitdir(gitdir); //when let_assert!( - Err(err) = validate_positions(&repository, &repo_details, repo_config.clone()), + Err(err) = + validate_positions(&*open_repository, &repo_details, repo_config.clone()), "validate" ); @@ -512,12 +559,16 @@ mod positions { git::push::Result::Err(git::push::Error::Lock) }, )); - let_assert!(Ok(repository) = test_repository.open(&gitdir), "open repo"); + let_assert!( + Ok(open_repository) = test_repository.open(&gitdir), + "open repo" + ); let repo_details = given::repo_details(&fs).with_gitdir(gitdir.clone()); //when let_assert!( - Err(err) = validate_positions(&repository, &repo_details, repo_config.clone()), + Err(err) = + validate_positions(&*open_repository, &repo_details, repo_config.clone()), "validate" ); @@ -561,12 +612,16 @@ mod positions { git::fetch::Result::Ok(()) }, )); - let_assert!(Ok(repository) = test_repository.open(&gitdir), "open repo"); + let_assert!( + Ok(open_repository) = test_repository.open(&gitdir), + "open repo" + ); let repo_details = given::repo_details(&fs).with_gitdir(gitdir.clone()); //when let_assert!( - Ok(positions) = validate_positions(&repository, &repo_details, repo_config.clone()), + Ok(positions) = + validate_positions(&*open_repository, &repo_details, repo_config.clone()), "validate" ); diff --git a/crates/repo-actor/src/branch.rs b/crates/repo-actor/src/branch.rs index d514b82..a373f71 100644 --- a/crates/repo-actor/src/branch.rs +++ b/crates/repo-actor/src/branch.rs @@ -16,7 +16,7 @@ pub async fn advance_next( dev_commit_history: Vec, repo_details: git::RepoDetails, repo_config: config::RepoConfig, - repository: git::OpenRepository, + open_repository: &dyn git::repository::OpenRepositoryLike, addr: Addr, message_token: MessageToken, ) { @@ -31,7 +31,7 @@ pub async fn advance_next( } info!("Advancing next to commit '{}'", commit); if let Err(err) = git::push::reset( - &repository, + open_repository, &repo_details, &repo_config.branches().next(), &commit.into(), @@ -81,11 +81,11 @@ pub async fn advance_main( next: git::Commit, repo_details: &git::RepoDetails, repo_config: &config::RepoConfig, - repository: &git::OpenRepository, + open_repository: &dyn git::repository::OpenRepositoryLike, ) { info!("Advancing main to next"); if let Err(err) = git::push::reset( - repository, + open_repository, repo_details, &repo_config.branches().main(), &next.into(), diff --git a/crates/repo-actor/src/lib.rs b/crates/repo-actor/src/lib.rs index 3bf1815..be59827 100644 --- a/crates/repo-actor/src/lib.rs +++ b/crates/repo-actor/src/lib.rs @@ -32,8 +32,8 @@ pub struct RepoActor { last_main_commit: Option, last_next_commit: Option, last_dev_commit: Option, - repository: git::Repository, - open_repository: Option, + repository: Box, + open_repository: Option>, net: Network, forge: forge::Forge, } @@ -43,7 +43,7 @@ impl RepoActor { webhook: config::server::Webhook, generation: git::Generation, net: Network, - repo: git::Repository, + repository: Box, ) -> Self { let forge = forge::Forge::new(details.clone(), net.clone()); debug!(?forge, "new"); @@ -57,7 +57,7 @@ impl RepoActor { last_main_commit: None, last_next_commit: None, last_dev_commit: None, - repository: repo, + repository, open_repository: None, net, forge, @@ -96,7 +96,7 @@ impl Handler for RepoActor { #[tracing::instrument(name = "RepoActor::CloneRepo", skip_all, fields(repo = %self.repo_details /*, gitdir = %self.repo_details.gitdir */))] fn handle(&mut self, _msg: CloneRepo, ctx: &mut Self::Context) -> Self::Result { let gitdir = self.repo_details.gitdir.clone(); - match git::repository::open(&self.repository, &self.repo_details, gitdir) { + match git::repository::open(&*self.repository, &self.repo_details, gitdir) { Ok(repository) => { self.open_repository.replace(repository); if self.repo_details.repo_config.is_none() { @@ -121,14 +121,15 @@ impl Handler for RepoActor { fn handle(&mut self, _msg: LoadConfigFromRepo, ctx: &mut Self::Context) -> Self::Result { let details = self.repo_details.clone(); let addr = ctx.address(); - let Some(open_repository) = self.open_repository.clone() else { + let Some(open_repository) = &self.open_repository else { warn!("missing open repository - can't load configuration"); return; }; - repo_actor::load::load_file(details, addr, open_repository) + let open_repository = open_repository.duplicate(); + async move { repo_actor::load::load_file(details, addr, &*open_repository).await } .in_current_span() .into_actor(self) - .wait(ctx); + .wait(ctx) } } @@ -195,15 +196,15 @@ impl Handler for RepoActor { .into_actor(self) .wait(ctx); } - if let (Some(repository), Some(repo_config)) = ( - self.open_repository.clone(), - self.repo_details.repo_config.clone(), - ) { + if let (Some(open_repository), Some(repo_config)) = + (&self.open_repository, self.repo_details.repo_config.clone()) + { let repo_details = self.repo_details.clone(); let addr = ctx.address(); let message_token = self.message_token; + let open_repository = open_repository.duplicate(); async move { - match validate_positions(&repository, &repo_details, repo_config) { + match validate_positions(&*open_repository, &repo_details, repo_config) { Ok(Positions { main, next, @@ -258,16 +259,22 @@ impl Handler for RepoActor { .into_actor(self) .wait(ctx); } else if dev_ahead_of_next { - if let Some(repository) = self.open_repository.clone() { - branch::advance_next( - msg.next, - msg.dev_commit_history, - self.repo_details.clone(), - repo_config, - repository, - addr, - self.message_token, - ) + if let Some(open_repository) = &self.open_repository { + let open_repository = open_repository.duplicate(); + let repo_details = self.repo_details.clone(); + let message_token = self.message_token; + async move { + branch::advance_next( + msg.next, + msg.dev_commit_history, + repo_details, + repo_config, + &*open_repository, + addr, + message_token, + ) + .await + } .in_current_span() .into_actor(self) .wait(ctx); @@ -304,15 +311,16 @@ impl Handler for RepoActor { warn!("No config loaded"); return; }; - let Some(repository) = self.open_repository.clone() else { + let Some(open_repository) = &self.open_repository else { warn!("No repository opened"); return; }; let repo_details = self.repo_details.clone(); let addr = ctx.address(); let message_token = self.message_token; + let open_repository = open_repository.duplicate(); async move { - branch::advance_main(msg.0, &repo_details, &repo_config, &repository).await; + branch::advance_main(msg.0, &repo_details, &repo_config, &*open_repository).await; match repo_config.source() { git_next_config::RepoConfigSource::Repo => addr.do_send(LoadConfigFromRepo), git_next_config::RepoConfigSource::Server => { diff --git a/crates/repo-actor/src/load.rs b/crates/repo-actor/src/load.rs index be389d1..ff64e32 100644 --- a/crates/repo-actor/src/load.rs +++ b/crates/repo-actor/src/load.rs @@ -15,10 +15,10 @@ use super::{LoadedConfig, RepoActor}; pub async fn load_file( repo_details: git::RepoDetails, addr: Addr, - open_repository: git::OpenRepository, + open_repository: &dyn git::repository::OpenRepositoryLike, ) { info!("Loading .git-next.toml from repo"); - let repo_config = match load(&repo_details, &open_repository).await { + let repo_config = match load(&repo_details, open_repository).await { Ok(repo_config) => repo_config, Err(err) => { error!(?err, "Failed to load config"); @@ -31,7 +31,7 @@ pub async fn load_file( async fn load( details: &git::RepoDetails, - open_repository: &git::OpenRepository, + open_repository: &dyn git::repository::OpenRepositoryLike, ) -> Result { let contents = open_repository.read_file(&details.branch, &PathBuf::from(".git-next.toml"))?; let config = config::RepoConfig::parse(&contents)?; @@ -50,7 +50,7 @@ pub enum Error { pub async fn validate( config: config::RepoConfig, - open_repository: &git::OpenRepository, + open_repository: &dyn git::repository::OpenRepositoryLike, ) -> Result { let branches = open_repository.remote_branches()?; if !branches diff --git a/crates/server/src/actors/server.rs b/crates/server/src/actors/server.rs index 68df2b9..8a1f549 100644 --- a/crates/server/src/actors/server.rs +++ b/crates/server/src/actors/server.rs @@ -7,7 +7,7 @@ use config::server::{ServerConfig, ServerStorage, Webhook}; use git_next_config::{ self as config, ForgeAlias, ForgeConfig, GitDir, RepoAlias, ServerRepoConfig, }; -use git_next_git::{Generation, RepoDetails, Repository}; +use git_next_git::{repository::RepositoryFactory, Generation, RepoDetails}; use git_next_repo_actor::{CloneRepo, RepoActor}; use kxio::{fs::FileSystem, network::Network}; use tracing::{error, info, warn}; @@ -38,7 +38,7 @@ pub struct Server { webhook: Option>, fs: FileSystem, net: Network, - repo: Repository, + repository_factory: Box, } impl Actor for Server { type Context = Context; @@ -114,14 +114,14 @@ impl Handler for Server { } } impl Server { - pub fn new(fs: FileSystem, net: Network, repo: Repository) -> Self { + pub fn new(fs: FileSystem, net: Network, repo: Box) -> Self { let generation = Generation::new(); Self { generation, webhook: None, fs, net, - repo, + repository_factory: repo, } } fn create_forge_data_directories( @@ -180,7 +180,7 @@ impl Server { let server_storage = server_storage.clone(); let webhook = webhook.clone(); let net = self.net.clone(); - let repo = self.repo.clone(); + let repository_factory = self.repository_factory.duplicate(); let generation = self.generation; move |(repo_alias, server_repo_config)| { let span = tracing::info_span!("create_actor", alias = %repo_alias, config = %server_repo_config); @@ -213,7 +213,7 @@ impl Server { webhook.clone(), generation, net.clone(), - repo.clone(), + repository_factory.duplicate(), ); (forge_name.clone(), repo_alias, actor) } diff --git a/crates/server/src/config/tests.rs b/crates/server/src/config/tests.rs index 6f829c3..8d2a241 100644 --- a/crates/server/src/config/tests.rs +++ b/crates/server/src/config/tests.rs @@ -60,9 +60,9 @@ fn repo_details_find_default_push_remote_finds_correct_remote() -> Result<()> { .with_hostname(Hostname::new("git.kemitix.net")); repo_details.repo_path = RepoPath::new("kemitix/git-next".to_string()); let gitdir = &repo_details.gitdir; - let repository = git::repository::new().open(gitdir)?; + let open_repository = git::repository::real().open(gitdir)?; let_assert!( - Some(found_git_remote) = repository.find_default_remote(Direction::Push), + Some(found_git_remote) = open_repository.find_default_remote(Direction::Push), "Default Push Remote not found" ); let config_git_remote = repo_details.git_remote(); @@ -85,14 +85,14 @@ fn gitdir_validate_should_pass_a_valid_git_repo() -> Result<()> { config::common::forge_details(1, ForgeType::MockForge), None, GitDir::new(root), // Server GitDir - should be ignored - ); + ) + .with_repo_path(RepoPath::new("kemitix/git-next".to_string())); repo_details.forge = repo_details .forge .with_hostname(Hostname::new("git.kemitix.net")); - repo_details.repo_path = RepoPath::new("kemitix/git-next".to_string()); let gitdir = &repo_details.gitdir; - let repository = git::repository::new().open(gitdir)?; - validate_repo(&repository, &repo_details)?; + let repository = git::repository::real().open(gitdir)?; + validate_repo(&*repository, &repo_details)?; Ok(()) } @@ -103,17 +103,17 @@ fn gitdir_validate_should_fail_a_git_repo_with_wrong_remote() -> Result<()> { Ok(cli_crate_dir) = std::env::current_dir().map_err(git::validation::repo::Error::Io) ); let_assert!(Some(Some(root)) = cli_crate_dir.parent().map(|p| p.parent())); - let mut repo_details = git::common::repo_details( + let repo_details = git::common::repo_details( 1, git::Generation::new(), config::common::forge_details(1, ForgeType::MockForge), None, GitDir::new(root), // Server GitDir - should be ignored - ); - repo_details.repo_path = RepoPath::new("hello/world".to_string()); + ) + .with_repo_path(RepoPath::new("hello/world".to_string())); let gitdir = &repo_details.gitdir; - let repository = git::repository::new().open(gitdir)?; - let_assert!(Err(_) = validate_repo(&repository, &repo_details)); + let repository = git::repository::real().open(gitdir)?; + let_assert!(Err(_) = validate_repo(&*repository, &repo_details)); Ok(()) } diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs index e264869..f0930b6 100644 --- a/crates/server/src/lib.rs +++ b/crates/server/src/lib.rs @@ -3,7 +3,7 @@ mod config; // use actix::prelude::*; -use git_next_git::Repository; +use git_next_git::repository::RepositoryFactory; use kxio::{fs::FileSystem, network::Network}; use std::path::PathBuf; @@ -37,7 +37,7 @@ pub fn init(fs: FileSystem) { } } -pub async fn start(fs: FileSystem, net: Network, repo: Repository) { +pub async fn start(fs: FileSystem, net: Network, repo: Box) { init_logging(); info!("Starting Server...");