refactor: create a RepositoryFactory trait
All checks were successful
Rust / build (push) Successful in 1m14s
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful

This commit is contained in:
Paul Campbell 2024-06-19 16:40:10 +01:00
parent ea20afee12
commit 94ad2c441c
22 changed files with 481 additions and 401 deletions

View file

@ -96,3 +96,4 @@ tokio = { version = "1.37", features = ["rt", "macros"] }
assert2 = "0.3"
pretty_assertions = "1.4"
rand = "0.8"
mockall = "0.12"

View file

@ -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 {

View file

@ -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"

View file

@ -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,

View file

@ -10,10 +10,10 @@ use crate as git;
use git_next_config as config;
#[derive(Debug, Default, Clone)]
pub struct MockRepository {
open_repos: Arc<Mutex<HashMap<config::GitDir, git::repository::MockOpenRepository>>>,
pub struct FakeRepository {
open_repos: Arc<Mutex<HashMap<config::GitDir, git::repository::FakeOpenRepository>>>,
}
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<git::repository::MockOpenRepository> {
pub fn get(&self, gitdir: &config::GitDir) -> Option<git::repository::FakeOpenRepository> {
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())))

View file

@ -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<OpenRepository> {
let repository = if !gitdir.exists() {
) -> Result<Box<dyn OpenRepositoryLike>> {
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<dyn RepositoryFactory>;
fn open(&self, gitdir: &GitDir) -> Result<Box<dyn OpenRepositoryLike>>;
fn git_clone(&self, repo_details: &RepoDetails) -> Result<Box<dyn OpenRepositoryLike>>;
}
pub fn real() -> Box<dyn RepositoryFactory> {
Box::new(RealRepositoryFactory)
}
pub fn mock() -> Box<MockRepositoryFactory> {
Box::new(MockRepositoryFactory::new())
}
#[derive(Debug, Clone)]
struct RealRepositoryFactory;
impl RepositoryFactory for RealRepositoryFactory {
fn open(&self, gitdir: &GitDir) -> Result<Box<dyn OpenRepositoryLike>> {
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<Box<dyn OpenRepositoryLike>> {
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<dyn RepositoryFactory> {
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 {

View file

@ -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<dyn OpenRepositoryLike>;
fn remote_branches(&self) -> git::push::Result<Vec<config::BranchName>>;
fn find_default_remote(&self, direction: Direction) -> Option<git::GitRemote>;
fn fetch(&self) -> Result<(), git::fetch::Error>;
@ -102,6 +104,11 @@ pub trait OpenRepositoryLike {
file_name: &Path,
) -> git::file::Result<String>;
}
pub fn mock() -> Box<MockOpenRepositoryLike> {
Box::new(MockOpenRepositoryLike::new())
}
impl std::ops::Deref for OpenRepository {
type Target = dyn OpenRepositoryLike;

View file

@ -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<Mutex<Option<git::GitRemote>>>,
default_fetch_remote: Arc<Mutex<Option<git::GitRemote>>>,
operations: Arc<Mutex<Vec<String>>>,
}
impl MockOpenRepository {
impl FakeOpenRepository {
pub fn new() -> Self {
Self::default()
}
@ -50,13 +50,13 @@ impl MockOpenRepository {
.unwrap_or_default()
}
}
impl From<MockOpenRepository> for git::OpenRepository {
fn from(value: MockOpenRepository) -> Self {
impl From<FakeOpenRepository> 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<Vec<config::BranchName>> {
todo!("MockOpenRepository::remote_branched")
}
@ -118,4 +118,8 @@ impl git::repository::OpenRepositoryLike for MockOpenRepository {
) -> git::file::Result<String> {
todo!("MockOpenRepository::read_file")
}
fn duplicate(&self) -> Box<dyn OpenRepositoryLike> {
Box::new(self.clone())
}
}

View file

@ -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<dyn OpenRepositoryLike> {
Box::new(self.clone())
}
}
fn as_gix_error(branch: BranchName) -> impl FnOnce(String) -> git::commit::log::Error {

View file

@ -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<String> {
self.real.read_file(branch_name, file_name)
}
fn duplicate(&self) -> Box<dyn OpenRepositoryLike> {
Box::new(self.clone())
}
}
impl TestOpenRepository {
pub fn new(

View file

@ -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;

View file

@ -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());
}
}

View file

@ -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};

View file

@ -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<Positions> {
@ -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<git::commit::Histories> {
let main = (repository.commit_log(&repo_config.branches().main(), &[]))?;

View file

@ -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();

View file

@ -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"
);

View file

@ -16,7 +16,7 @@ pub async fn advance_next(
dev_commit_history: Vec<git::Commit>,
repo_details: git::RepoDetails,
repo_config: config::RepoConfig,
repository: git::OpenRepository,
open_repository: &dyn git::repository::OpenRepositoryLike,
addr: Addr<super::RepoActor>,
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(),

View file

@ -32,8 +32,8 @@ pub struct RepoActor {
last_main_commit: Option<git::Commit>,
last_next_commit: Option<git::Commit>,
last_dev_commit: Option<git::Commit>,
repository: git::Repository,
open_repository: Option<git::OpenRepository>,
repository: Box<dyn git::repository::RepositoryFactory>,
open_repository: Option<Box<dyn git::repository::OpenRepositoryLike>>,
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<dyn git::repository::RepositoryFactory>,
) -> 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<CloneRepo> 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<LoadConfigFromRepo> 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<ValidateRepo> 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<StartMonitoring> 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<AdvanceMainTo> 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 => {

View file

@ -15,10 +15,10 @@ use super::{LoadedConfig, RepoActor};
pub async fn load_file(
repo_details: git::RepoDetails,
addr: Addr<RepoActor>,
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<config::RepoConfig, Error> {
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<config::RepoConfig, Error> {
let branches = open_repository.remote_branches()?;
if !branches

View file

@ -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<Addr<WebhookActor>>,
fs: FileSystem,
net: Network,
repo: Repository,
repository_factory: Box<dyn RepositoryFactory>,
}
impl Actor for Server {
type Context = Context<Self>;
@ -114,14 +114,14 @@ impl Handler<ServerConfig> for Server {
}
}
impl Server {
pub fn new(fs: FileSystem, net: Network, repo: Repository) -> Self {
pub fn new(fs: FileSystem, net: Network, repo: Box<dyn RepositoryFactory>) -> 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)
}

View file

@ -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(())
}

View file

@ -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<dyn RepositoryFactory>) {
init_logging();
info!("Starting Server...");