WIP: test(git): make repository more testable
This commit is contained in:
parent
c3a5e50ad5
commit
2f512e52ae
12 changed files with 166 additions and 66 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,11 +1,124 @@
|
|||
//
|
||||
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<OpenRepository, Error>;
|
||||
fn clone(&self, repo_details: &RepoDetails) -> Result<OpenRepository, Error>;
|
||||
}
|
||||
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<OpenRepository, Error> {
|
||||
Ok(OpenRepository::Real(RealOpenRepository(
|
||||
gix::ThreadSafeRepository::open(gitdir.to_path_buf())?.to_thread_local(),
|
||||
)))
|
||||
}
|
||||
|
||||
fn clone(&self, repo_details: &RepoDetails) -> Result<OpenRepository, Error> {
|
||||
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<OpenRepository, Error> {
|
||||
Ok(OpenRepository::Mock)
|
||||
}
|
||||
|
||||
fn clone(&self, _repo_details: &RepoDetails) -> Result<OpenRepository, Error> {
|
||||
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<GitRemote>;
|
||||
}
|
||||
impl OpenRepositoryLike for OpenRepository {
|
||||
fn find_default_remote(&self, direction: Direction) -> Option<GitRemote> {
|
||||
match self {
|
||||
OpenRepository::Real(real) => real.find_default_remote(direction),
|
||||
OpenRepository::Mock => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RealOpenRepository(gix::Repository);
|
||||
impl OpenRepositoryLike for RealOpenRepository {
|
||||
fn find_default_remote(&self, direction: Direction) -> Option<GitRemote> {
|
||||
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()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Direction {
|
||||
/// Push local changes to the remote.
|
||||
Push,
|
||||
/// Fetch changes from the remote to the local repository.
|
||||
Fetch,
|
||||
}
|
||||
impl From<Direction> for gix::remote::Direction {
|
||||
fn from(value: Direction) -> Self {
|
||||
match value {
|
||||
Direction::Push => Self::Push,
|
||||
Direction::Fetch => Self::Fetch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, derive_more::From, derive_more::Deref)]
|
||||
pub struct Repository(gix::ThreadSafeRepository);
|
||||
impl Repository {
|
||||
// TODO: #[deprecated(note = "Use Repository::Real::open() or Repository::Real::clone()")]
|
||||
pub struct LegacyRepository(gix::ThreadSafeRepository);
|
||||
impl LegacyRepository {
|
||||
pub fn open(gitdir: impl Into<PathBuf>) -> Result<Self, Error> {
|
||||
Ok(Self(gix::ThreadSafeRepository::open(gitdir.into())?))
|
||||
}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
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) else {
|
||||
return Err(Error::NoDefaultPushRemote);
|
||||
};
|
||||
let Some(fetch_remote) = repository.find_default_remote(Direction::Fetch) else {
|
||||
return Err(Error::NoDefaultFetchRemote);
|
||||
};
|
||||
info!(config = %git_remote, push = %push_remote, fetch = %fetch_remote, "Check remotes match");
|
||||
if git_remote != push_remote {
|
||||
return Err(Error::MismatchDefaultPushRemote {
|
||||
|
@ -27,35 +29,11 @@ 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<GitRemote> {
|
||||
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<T> = core::result::Result<T, Error>;
|
||||
#[derive(Debug, derive_more::Display)]
|
||||
pub enum Error {
|
||||
NoDefaultPushRemote,
|
||||
NoDefaultFetchRemote,
|
||||
NoUrlForDefaultPushRemote,
|
||||
NoHostnameForDefaultPushRemote,
|
||||
UnableToOpenRepo(String),
|
||||
|
|
|
@ -18,7 +18,7 @@ pub async fn advance_next(
|
|||
dev_commit_history: Vec<git::Commit>,
|
||||
repo_config: RepoConfig,
|
||||
forge: gitforge::Forge,
|
||||
repository: git::Repository,
|
||||
repository: git::LegacyRepository,
|
||||
addr: Addr<super::RepoActor>,
|
||||
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<RepoActor>,
|
||||
message_token: super::MessageToken,
|
||||
) {
|
||||
|
|
|
@ -28,7 +28,7 @@ pub struct RepoActor {
|
|||
last_main_commit: Option<git::Commit>,
|
||||
last_next_commit: Option<git::Commit>,
|
||||
last_dev_commit: Option<git::Commit>,
|
||||
repository: Option<git::Repository>,
|
||||
repository: Option<git::LegacyRepository>,
|
||||
net: Network,
|
||||
forge: gitforge::Forge,
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use assert2::let_assert;
|
||||
use git::repository::{Direction, OpenRepositoryLike};
|
||||
use git_next_config::{
|
||||
ForgeType, GitDir, Hostname, RepoBranches, RepoConfig, RepoConfigSource, RepoPath,
|
||||
ServerRepoConfig,
|
||||
};
|
||||
use git_next_git::{self as git, Generation, GitRemote, Repository};
|
||||
use gix::remote::Direction;
|
||||
use git_next_git::{self as git, Generation, GitRemote, LegacyRepository};
|
||||
use kxio::fs;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::gitforge::tests::common;
|
||||
use crate::{config, gitforge::tests::common};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -179,8 +179,11 @@ 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 found_git_remote = git::validate::find_default_remote(&repository, Direction::Push)?;
|
||||
let repository = git::repository::new().open(gitdir)?;
|
||||
let_assert!(
|
||||
Some(found_git_remote) = repository.find_default_remote(Direction::Push),
|
||||
"Default Push Remote not found"
|
||||
);
|
||||
let config_git_remote = repo_details.git_remote();
|
||||
|
||||
assert_eq!(
|
||||
|
@ -207,7 +210,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 = git::repository::new().open(gitdir)?;
|
||||
git::validate(&repository, &repo_details)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -226,7 +229,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 = git::repository::new().open(gitdir)?;
|
||||
let_assert!(Err(_) = git::validate(&repository, &repo_details));
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -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<ValidatedPositions, Error> {
|
||||
let repo_details = &forge.repo_details;
|
||||
|
|
|
@ -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<RepoActor>,
|
||||
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<Repository, git::repository::Error> {
|
||||
fn repo_clone(&self, gitdir: GitDir) -> Result<LegacyRepository, git::repository::Error> {
|
||||
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)
|
||||
|
|
|
@ -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<RepoActor>,
|
||||
_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<Repository, git::repository::Error> {
|
||||
fn repo_clone(&self, _gitdir: GitDir) -> Result<LegacyRepository, git::repository::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<super::actors::repo::RepoActor>,
|
||||
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<Repository, git::repository::Error>;
|
||||
fn repo_clone(&self, gitdir: GitDir) -> Result<LegacyRepository, git::repository::Error>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
Loading…
Reference in a new issue