diff --git a/crates/git/src/fetch.rs b/crates/git/src/fetch.rs index 4d561de..667bb59 100644 --- a/crates/git/src/fetch.rs +++ b/crates/git/src/fetch.rs @@ -2,7 +2,7 @@ use std::ops::Deref; use tracing::{debug, info}; -use super::{RepoDetails, Repository}; +use super::{LegacyRepository, RepoDetails}; #[derive(Debug, derive_more::From, derive_more::Display)] pub enum Error { @@ -14,7 +14,7 @@ pub enum Error { impl std::error::Error for Error {} #[tracing::instrument(skip_all, fields(repo = %repo_details))] -pub fn fetch(repository: &Repository, repo_details: &RepoDetails) -> Result<(), Error> { +pub fn fetch(repository: &LegacyRepository, repo_details: &RepoDetails) -> Result<(), Error> { let repository = repository.deref().to_thread_local(); let Some(remote) = repository.find_default_remote(gix::remote::Direction::Fetch) else { return Err(Error::NoFetchRemoteFound); diff --git a/crates/git/src/lib.rs b/crates/git/src/lib.rs index 6427946..a33c567 100644 --- a/crates/git/src/lib.rs +++ b/crates/git/src/lib.rs @@ -19,5 +19,6 @@ pub use git_ref::GitRef; pub use git_remote::GitRemote; pub use push::push; pub use repo_details::RepoDetails; +pub use repository::LegacyRepository; pub use repository::Repository; pub use validate::validate; diff --git a/crates/git/src/push.rs b/crates/git/src/push.rs index c4a506a..a27a45d 100644 --- a/crates/git/src/push.rs +++ b/crates/git/src/push.rs @@ -4,7 +4,7 @@ use git_next_config::BranchName; use secrecy::ExposeSecret; use tracing::{info, warn}; -use super::{GitRef, RepoDetails, Repository}; +use super::{GitRef, LegacyRepository, RepoDetails}; #[derive(Debug)] pub enum Force { @@ -23,7 +23,7 @@ impl std::fmt::Display for Force { // TODO: (#72) reimplement using `gix` #[tracing::instrument(skip_all, fields(branch = %branch_name, to = %to_commit, force = %force))] pub fn push( - repository: &Repository, + repository: &LegacyRepository, repo_details: &RepoDetails, branch_name: BranchName, to_commit: GitRef, diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index b3562c7..81b5288 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -1,11 +1,123 @@ // use std::{ops::Deref as _, path::PathBuf, sync::atomic::AtomicBool}; +use git_next_config::{GitDir, Hostname, RepoPath}; + +use crate::GitRemote; + use super::RepoDetails; +pub enum Repository { + Real, + Mock, +} +pub const fn new() -> Repository { + Repository::Real +} +pub const fn mock() -> Repository { + Repository::Mock +} +pub trait RepositoryLike { + fn open(&self, gitdir: &GitDir) -> Result; + fn clone(&self, repo_details: &RepoDetails) -> Result; +} +impl std::ops::Deref for Repository { + type Target = dyn RepositoryLike; + + fn deref(&self) -> &Self::Target { + match self { + Self::Real => &RealRepository, + Self::Mock => todo!(), + } + } +} +struct RealRepository; +impl RepositoryLike for RealRepository { + fn open(&self, gitdir: &GitDir) -> Result { + Ok(OpenRepository::Real(RealOpenRepository( + gix::ThreadSafeRepository::open(gitdir.to_path_buf())?.to_thread_local(), + ))) + } + + fn clone(&self, repo_details: &RepoDetails) -> Result { + use secrecy::ExposeSecret; + let origin = repo_details.origin(); + let (repository, _outcome) = + gix::prepare_clone_bare(origin.expose_secret().as_str(), repo_details.gitdir.deref())? + .fetch_only(gix::progress::Discard, &AtomicBool::new(false))?; + + Ok(OpenRepository::Real(RealOpenRepository(repository))) + } +} +struct MockRepository; +impl RepositoryLike for MockRepository { + fn open(&self, _gitdir: &GitDir) -> Result { + Ok(OpenRepository::Mock) + } + + fn clone(&self, _repo_details: &RepoDetails) -> Result { + Ok(OpenRepository::Mock) + } +} + +pub enum OpenRepository { + Real(RealOpenRepository), + Mock, // TODO: contain a mock model of a repo +} +pub trait OpenRepositoryLike { + fn find_default_remote(&self, direction: Direction) -> Option; +} +impl OpenRepositoryLike for OpenRepository { + fn find_default_remote(&self, direction: Direction) -> Option { + match self { + OpenRepository::Real(real) => real.find_default_remote(direction), + OpenRepository::Mock => todo!(), + } + } +} + +struct RealOpenRepository(gix::Repository); +impl OpenRepositoryLike for RealOpenRepository { + fn find_default_remote(&self, direction: Direction) -> Option { + let repository = &self.0; + let Some(Ok(remote)) = repository.find_default_remote(direction.into()) else { + return None; + }; + let Some(url) = remote.url(direction.into()) else { + return None; + }; + let Some(host) = url.host() else { + return None; + }; + let path = url.path.to_string(); + let path = path.strip_prefix('/').map_or(path.as_str(), |path| path); + let path = path.strip_suffix(".git").map_or(path, |path| path); + Some(GitRemote::new( + Hostname::new(host), + RepoPath::new(path.to_string()), + )) + } +} + +pub enum Direction { + /// Push local changes to the remote. + Push, + /// Fetch changes from the remote to the local repository. + Fetch, +} +impl From for gix::remote::Direction { + fn from(value: Direction) -> Self { + match value { + Direction::Push => gix::remote::Direction::Push, + Direction::Fetch => gix::remote::Direction::Fetch, + } + } +} + #[derive(Debug, Clone, derive_more::From, derive_more::Deref)] -pub struct Repository(gix::ThreadSafeRepository); -impl Repository { +#[deprecated(note = "Use Repository::Real::open() or Repository::Real::clone()")] +pub struct LegacyRepository(gix::ThreadSafeRepository); +impl LegacyRepository { pub fn open(gitdir: impl Into) -> Result { Ok(Self(gix::ThreadSafeRepository::open(gitdir.into())?)) } diff --git a/crates/git/src/validate.rs b/crates/git/src/validate.rs index e86fb80..9240c22 100644 --- a/crates/git/src/validate.rs +++ b/crates/git/src/validate.rs @@ -1,16 +1,14 @@ -use std::ops::Deref as _; - -use git_next_config::{Hostname, RepoPath}; -use gix::remote::Direction; use tracing::info; -use super::{GitRemote, RepoDetails, Repository}; +use crate::repository::{Direction, OpenRepository, OpenRepositoryLike}; + +use super::{GitRemote, RepoDetails}; #[tracing::instrument(skip_all)] -pub fn validate(repository: &Repository, repo_details: &RepoDetails) -> Result<()> { +pub fn validate(repository: &OpenRepository, repo_details: &RepoDetails) -> Result<()> { let git_remote = repo_details.git_remote(); - let push_remote = find_default_remote(repository, Direction::Push)?; - let fetch_remote = find_default_remote(repository, Direction::Fetch)?; + let Some(push_remote) = repository.find_default_remote(Direction::Push); + let Some(fetch_remote) = repository.find_default_remote(Direction::Fetch); info!(config = %git_remote, push = %push_remote, fetch = %fetch_remote, "Check remotes match"); if git_remote != push_remote { return Err(Error::MismatchDefaultPushRemote { @@ -27,31 +25,6 @@ pub fn validate(repository: &Repository, repo_details: &RepoDetails) -> Result<( Ok(()) } -#[tracing::instrument(skip_all, fields(?direction))] -pub fn find_default_remote( - repository: &Repository, - direction: gix::remote::Direction, -) -> Result { - let repository = repository.deref().to_thread_local(); - let Some(Ok(remote)) = repository.find_default_remote(gix::remote::Direction::Push) else { - return Err(Error::NoDefaultPushRemote); - }; - let Some(url) = remote.url(direction) else { - return Err(Error::NoUrlForDefaultPushRemote); - }; - let Some(host) = url.host() else { - return Err(Error::NoHostnameForDefaultPushRemote); - }; - let path = url.path.to_string(); - let path = path.strip_prefix('/').map_or(path.as_str(), |path| path); - let path = path.strip_suffix(".git").map_or(path, |path| path); - info!(%host, %path, "found"); - Ok(GitRemote::new( - Hostname::new(host), - RepoPath::new(path.to_string()), - )) -} - type Result = core::result::Result; #[derive(Debug, derive_more::Display)] pub enum Error { diff --git a/crates/server/src/actors/repo/branch.rs b/crates/server/src/actors/repo/branch.rs index 0455e44..5c8337a 100644 --- a/crates/server/src/actors/repo/branch.rs +++ b/crates/server/src/actors/repo/branch.rs @@ -18,7 +18,7 @@ pub async fn advance_next( dev_commit_history: Vec, repo_config: RepoConfig, forge: gitforge::Forge, - repository: git::Repository, + repository: git::LegacyRepository, addr: Addr, message_token: super::MessageToken, ) { @@ -82,7 +82,7 @@ pub async fn advance_main( next: git::Commit, repo_config: RepoConfig, forge: gitforge::Forge, - repository: git::Repository, + repository: git::LegacyRepository, addr: Addr, message_token: super::MessageToken, ) { diff --git a/crates/server/src/actors/repo/mod.rs b/crates/server/src/actors/repo/mod.rs index e6689f7..1b8ad54 100644 --- a/crates/server/src/actors/repo/mod.rs +++ b/crates/server/src/actors/repo/mod.rs @@ -28,7 +28,7 @@ pub struct RepoActor { last_main_commit: Option, last_next_commit: Option, last_dev_commit: Option, - repository: Option, + repository: Option, net: Network, forge: gitforge::Forge, } diff --git a/crates/server/src/config/tests.rs b/crates/server/src/config/tests.rs index de5572b..dfb1d7b 100644 --- a/crates/server/src/config/tests.rs +++ b/crates/server/src/config/tests.rs @@ -1,11 +1,12 @@ use std::collections::BTreeMap; use assert2::let_assert; +use git::repository::Repository; use git_next_config::{ ForgeType, GitDir, Hostname, RepoBranches, RepoConfig, RepoConfigSource, RepoPath, ServerRepoConfig, }; -use git_next_git::{self as git, Generation, GitRemote, Repository}; +use git_next_git::{self as git, Generation, GitRemote, LegacyRepository}; use gix::remote::Direction; use kxio::fs; use pretty_assertions::assert_eq; @@ -179,7 +180,7 @@ 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 = Repository::open(gitdir)?; + let repository = git::repository::new().open(gitdir)?; let found_git_remote = git::validate::find_default_remote(&repository, Direction::Push)?; let config_git_remote = repo_details.git_remote(); @@ -207,7 +208,7 @@ fn gitdir_validate_should_pass_a_valid_git_repo() -> 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 = Repository::open(gitdir)?; + let repository = LegacyRepository::open(gitdir)?; git::validate(&repository, &repo_details)?; Ok(()) @@ -226,7 +227,7 @@ fn gitdir_validate_should_fail_a_git_repo_with_wrong_remote() -> Result<()> { ); repo_details.repo_path = RepoPath::new("hello/world".to_string()); let gitdir = &repo_details.gitdir; - let repository = Repository::open(gitdir)?; + let repository = LegacyRepository::open(gitdir)?; let_assert!(Err(_) = git::validate(&repository, &repo_details)); Ok(()) diff --git a/crates/server/src/gitforge/forgejo/branch/validate_positions.rs b/crates/server/src/gitforge/forgejo/branch/validate_positions.rs index baca461..ed514af 100644 --- a/crates/server/src/gitforge/forgejo/branch/validate_positions.rs +++ b/crates/server/src/gitforge/forgejo/branch/validate_positions.rs @@ -28,7 +28,7 @@ pub struct ValidatedPositions { pub async fn validate_positions( forge: &gitforge::forgejo::ForgeJoEnv, - repository: &git::Repository, + repository: &git::LegacyRepository, repo_config: RepoConfig, ) -> Result { let repo_details = &forge.repo_details; diff --git a/crates/server/src/gitforge/forgejo/mod.rs b/crates/server/src/gitforge/forgejo/mod.rs index ef7a7fa..0e38960 100644 --- a/crates/server/src/gitforge/forgejo/mod.rs +++ b/crates/server/src/gitforge/forgejo/mod.rs @@ -6,7 +6,7 @@ use std::time::Duration; use actix::prelude::*; use git_next_config::{BranchName, GitDir, RepoConfig}; -use git_next_git::{self as git, GitRef, RepoDetails, Repository}; +use git_next_git::{self as git, GitRef, LegacyRepository, RepoDetails, Repository}; use kxio::network::{self, Network}; use tracing::{error, info, warn}; @@ -21,10 +21,15 @@ struct ForgeJo; pub struct ForgeJoEnv { repo_details: RepoDetails, net: Network, + repo: Repository, } impl ForgeJoEnv { - pub(super) const fn new(repo_details: RepoDetails, net: Network) -> Self { - Self { repo_details, net } + pub(super) const fn new(repo_details: RepoDetails, net: Network, repo: Repository) -> Self { + Self { + repo_details, + net, + repo, + } } } #[async_trait::async_trait] @@ -47,7 +52,7 @@ impl super::ForgeLike for ForgeJoEnv { async fn branches_validate_positions( &self, - repository: git::Repository, + repository: git::LegacyRepository, repo_config: RepoConfig, addr: Addr, message_token: MessageToken, @@ -71,7 +76,7 @@ impl super::ForgeLike for ForgeJoEnv { fn branch_reset( &self, - repository: &git::Repository, + repository: &git::LegacyRepository, branch_name: BranchName, to_commit: GitRef, force: git::push::Force, @@ -130,12 +135,12 @@ impl super::ForgeLike for ForgeJoEnv { } } - fn repo_clone(&self, gitdir: GitDir) -> Result { + fn repo_clone(&self, gitdir: GitDir) -> Result { let repository = if !gitdir.exists() { info!("Local copy not found - cloning..."); - Repository::clone(&self.repo_details)? + self.repo.clone(&self.repo_details)? } else { - Repository::open(gitdir)? + self.repo.open(&gitdir)? }; info!("Validating..."); git::validate(&repository, &self.repo_details) diff --git a/crates/server/src/gitforge/mock_forge.rs b/crates/server/src/gitforge/mock_forge.rs index 830e85e..bc41640 100644 --- a/crates/server/src/gitforge/mock_forge.rs +++ b/crates/server/src/gitforge/mock_forge.rs @@ -1,5 +1,5 @@ use git_next_config::{BranchName, GitDir, RepoConfig}; -use git_next_git::{self as git, GitRef, Repository}; +use git_next_git::{self as git, GitRef, LegacyRepository}; use crate::{actors::repo::RepoActor, gitforge, types::MessageToken}; @@ -31,7 +31,7 @@ impl super::ForgeLike for MockForgeEnv { async fn branches_validate_positions( &self, - _repository: Repository, + _repository: LegacyRepository, _repo_config: RepoConfig, _addr: actix::prelude::Addr, _message_token: MessageToken, @@ -41,7 +41,7 @@ impl super::ForgeLike for MockForgeEnv { fn branch_reset( &self, - _repository: &Repository, + _repository: &LegacyRepository, _branch_name: BranchName, _to_commit: GitRef, _force: git::push::Force, @@ -53,7 +53,7 @@ impl super::ForgeLike for MockForgeEnv { todo!() } - fn repo_clone(&self, _gitdir: GitDir) -> Result { + fn repo_clone(&self, _gitdir: GitDir) -> Result { todo!() } } diff --git a/crates/server/src/gitforge/mod.rs b/crates/server/src/gitforge/mod.rs index 824f84f..528725b 100644 --- a/crates/server/src/gitforge/mod.rs +++ b/crates/server/src/gitforge/mod.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use git_next_config::{BranchName, GitDir, RepoConfig}; -use git_next_git::{self as git, GitRef, RepoDetails, Repository}; +use git_next_git::{self as git, GitRef, LegacyRepository, RepoDetails}; use kxio::network::Network; #[cfg(feature = "forgejo")] @@ -38,7 +38,7 @@ pub trait ForgeLike { /// positions as needed. async fn branches_validate_positions( &self, - repository: Repository, + repository: LegacyRepository, repo_config: RepoConfig, addr: actix::prelude::Addr, message_token: MessageToken, @@ -47,7 +47,7 @@ pub trait ForgeLike { /// Moves a branch to a new commit. fn branch_reset( &self, - repository: &Repository, + repository: &LegacyRepository, branch_name: BranchName, to_commit: GitRef, force: git::push::Force, @@ -57,7 +57,7 @@ pub trait ForgeLike { async fn commit_status(&self, commit: &git::Commit) -> CommitStatus; /// Clones a repo to disk. - fn repo_clone(&self, gitdir: GitDir) -> Result; + fn repo_clone(&self, gitdir: GitDir) -> Result; } #[derive(Clone, Debug)]