test(git): make repository more testable
Some checks failed
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/push-next Pipeline failed
Rust / build (push) Has been cancelled

Adds a layer around Repository to allow the use of a mock.

Mock has still to be implemented.
This commit is contained in:
Paul Campbell 2024-05-18 11:41:18 +01:00
parent 6757723b77
commit 6f45316990
18 changed files with 303 additions and 171 deletions

View file

@ -5,6 +5,7 @@ edition = { workspace = true }
[dependencies] [dependencies]
git-next-server = { workspace = true } git-next-server = { workspace = true }
git-next-git = { workspace = true }
# CLI parsing # CLI parsing
clap = { workspace = true } clap = { workspace = true }

View file

@ -30,6 +30,7 @@ enum Server {
async fn main() { async fn main() {
let fs = fs::new(PathBuf::default()); let fs = fs::new(PathBuf::default());
let net = Network::new_real(); let net = Network::new_real();
let repo = git_next_git::repository::new();
let commands = Commands::parse(); let commands = Commands::parse();
match commands.command { match commands.command {
@ -41,7 +42,7 @@ async fn main() {
git_next_server::init(fs); git_next_server::init(fs);
} }
Server::Start => { Server::Start => {
git_next_server::start(fs, net).await; git_next_server::start(fs, net, repo).await;
} }
}, },
} }

View file

@ -381,8 +381,6 @@ mod forge_type {
mod gitdir { mod gitdir {
use std::path::PathBuf; use std::path::PathBuf;
use derive_more::Deref;
use crate::GitDir; use crate::GitDir;
#[test] #[test]

View file

@ -1,36 +1,9 @@
use std::ops::Deref; #[derive(Debug, derive_more::Display)]
use tracing::{debug, info};
use super::{RepoDetails, Repository};
#[derive(Debug, derive_more::From, derive_more::Display)]
pub enum Error { pub enum Error {
UnableToOpenRepo(Box<gix::open::Error>), UnableToOpenRepo(String),
NoFetchRemoteFound, NoFetchRemoteFound,
Connect(Box<gix::remote::connect::Error>), Connect(String),
Fetch(String), Fetch(String),
Lock,
} }
impl std::error::Error for 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> {
let repository = repository.deref().to_thread_local();
let Some(remote) = repository.find_default_remote(gix::remote::Direction::Fetch) else {
return Err(Error::NoFetchRemoteFound);
};
debug!(?remote, "fetch remote");
remote
.map_err(|e| Error::Fetch(e.to_string()))?
.connect(gix::remote::Direction::Fetch)
.map_err(Box::new)?
.prepare_fetch(gix::progress::Discard, Default::default())
.map_err(|e| Error::Fetch(e.to_string()))?
.receive(gix::progress::Discard, &Default::default())
.map_err(|e| Error::Fetch(e.to_string()))?;
info!("fetched");
Ok(())
}

View file

@ -13,11 +13,10 @@ pub mod validate;
mod tests; mod tests;
pub use commit::Commit; pub use commit::Commit;
pub use fetch::fetch;
pub use generation::Generation; pub use generation::Generation;
pub use git_ref::GitRef; pub use git_ref::GitRef;
pub use git_remote::GitRemote; pub use git_remote::GitRemote;
pub use push::push;
pub use repo_details::RepoDetails; pub use repo_details::RepoDetails;
pub use repository::open::OpenRepository;
pub use repository::Repository; pub use repository::Repository;
pub use validate::validate; pub use validate::validate;

View file

@ -1,10 +1,4 @@
use std::ops::Deref; use super::GitRef;
use git_next_config::BranchName;
use secrecy::ExposeSecret;
use tracing::{info, warn};
use super::{GitRef, RepoDetails, Repository};
#[derive(Debug)] #[derive(Debug)]
pub enum Force { pub enum Force {
@ -20,60 +14,12 @@ 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,
repo_details: &RepoDetails,
branch_name: BranchName,
to_commit: GitRef,
force: Force,
) -> Result {
let origin = repo_details.origin();
let force = match force {
Force::No => "".to_string(),
Force::From(old_ref) => format!("--force-with-lease={branch_name}:{old_ref}"),
};
// INFO: never log the command as it contains the API token within the 'origin'
let command: secrecy::Secret<String> = format!(
"/usr/bin/git push {} {to_commit}:{branch_name} {force}",
origin.expose_secret()
)
.into();
let git_dir = repository.deref().git_dir();
let ctx = gix::diff::command::Context {
git_dir: Some(git_dir.to_path_buf()),
..Default::default()
};
match gix::command::prepare(command.expose_secret())
.with_context(ctx)
.with_shell_allow_argument_splitting()
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
{
Ok(mut child) => match child.wait() {
Ok(_) => {
info!("Branch updated");
Ok(())
}
Err(err) => {
warn!(?err, ?git_dir, "Failed (wait)");
Err(Error::Push)
}
},
Err(err) => {
warn!(?err, ?git_dir, "Failed (spawn)");
Err(Error::Push)
}
}
}
#[derive(Debug, derive_more::From, derive_more::Display)] #[derive(Debug, derive_more::From, derive_more::Display)]
pub enum Error { pub enum Error {
Open(Box<gix::open::Error>), Open(Box<gix::open::Error>),
Fetch(super::fetch::Error), Fetch(super::fetch::Error),
Push, Push,
Lock,
} }
impl std::error::Error for Error {} impl std::error::Error for Error {}

View file

@ -1,11 +1,229 @@
// //
use std::{ops::Deref as _, path::PathBuf, sync::atomic::AtomicBool}; use std::{
ops::Deref as _,
path::PathBuf,
sync::{atomic::AtomicBool, Arc, Mutex},
};
use git_next_config::{BranchName, GitDir, Hostname, RepoPath};
use tracing::{info, warn};
use crate::{fetch, push, GitRef, GitRemote};
use super::RepoDetails; use super::RepoDetails;
#[derive(Clone, Copy, Debug)]
pub enum Repository {
Real,
Mock,
}
pub const fn new() -> Repository {
Repository::Real
}
pub const fn mock() -> Repository {
Repository::Mock
}
mod mock {
use super::*;
pub struct MockRepository;
impl RepositoryLike for MockRepository {
fn open(&self, _gitdir: &GitDir) -> Result<open::OpenRepository, Error> {
Ok(open::OpenRepository::Mock)
}
fn git_clone(&self, _repo_details: &RepoDetails) -> Result<open::OpenRepository, Error> {
Ok(open::OpenRepository::Mock)
}
}
}
pub trait RepositoryLike {
fn open(&self, gitdir: &GitDir) -> Result<open::OpenRepository, Error>;
fn git_clone(&self, repo_details: &RepoDetails) -> Result<open::OpenRepository, Error>;
}
impl std::ops::Deref for Repository {
type Target = dyn RepositoryLike;
fn deref(&self) -> &Self::Target {
match self {
Self::Real => &real::RealRepository,
Self::Mock => &mock::MockRepository,
}
}
}
mod real {
use super::*;
pub struct RealRepository;
impl RepositoryLike for RealRepository {
fn open(&self, gitdir: &GitDir) -> Result<open::OpenRepository, Error> {
Ok(open::OpenRepository::Real(open::RealOpenRepository::new(
Arc::new(Mutex::new(
gix::ThreadSafeRepository::open(gitdir.to_path_buf())?.to_thread_local(),
)),
)))
}
fn git_clone(&self, repo_details: &RepoDetails) -> Result<open::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(open::OpenRepository::Real(open::RealOpenRepository::new(
Arc::new(Mutex::new(repository)),
)))
}
}
}
pub mod open {
use super::*;
#[derive(Clone, Debug)]
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>;
fn fetch(&self) -> Result<(), fetch::Error>;
fn push(
&self,
repo_details: &RepoDetails,
branch_name: BranchName,
to_commit: GitRef,
force: push::Force,
) -> Result<(), push::Error>;
}
impl std::ops::Deref for OpenRepository {
type Target = dyn OpenRepositoryLike;
fn deref(&self) -> &Self::Target {
match self {
Self::Real(real) => real,
Self::Mock => todo!(),
}
}
}
#[derive(Clone, Debug, derive_more::Constructor)]
pub struct RealOpenRepository(Arc<Mutex<gix::Repository>>);
impl OpenRepositoryLike for RealOpenRepository {
fn find_default_remote(&self, direction: Direction) -> Option<GitRemote> {
let Ok(repository) = self.0.lock() else {
return None;
};
let Some(Ok(remote)) = repository.find_default_remote(direction.into()) else {
return None;
};
let url = remote.url(direction.into())?;
let host = url.host()?;
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()),
))
}
fn fetch(&self) -> Result<(), fetch::Error> {
let Ok(repository) = self.0.lock() else {
return Err(fetch::Error::Lock);
};
let Some(Ok(remote)) = repository.find_default_remote(Direction::Fetch.into()) else {
return Err(fetch::Error::NoFetchRemoteFound);
};
remote
.connect(gix::remote::Direction::Fetch)
.map_err(|e| fetch::Error::Connect(e.to_string()))?
.prepare_fetch(gix::progress::Discard, Default::default())
.map_err(|e| fetch::Error::Fetch(e.to_string()))?
.receive(gix::progress::Discard, &Default::default())
.map_err(|e| fetch::Error::Fetch(e.to_string()))?;
Ok(())
}
// TODO: (#72) reimplement using `gix`
fn push(
&self,
repo_details: &RepoDetails,
branch_name: BranchName,
to_commit: GitRef,
force: push::Force,
) -> Result<(), push::Error> {
let origin = repo_details.origin();
let force = match force {
push::Force::No => "".to_string(),
push::Force::From(old_ref) => format!("--force-with-lease={branch_name}:{old_ref}"),
};
// INFO: never log the command as it contains the API token within the 'origin'
use secrecy::ExposeSecret;
let command: secrecy::Secret<String> = format!(
"/usr/bin/git push {} {to_commit}:{branch_name} {force}",
origin.expose_secret()
)
.into();
let git_dir = self
.0
.lock()
.map_err(|_| push::Error::Lock)
.map(|r| r.git_dir().to_path_buf())?;
let ctx = gix::diff::command::Context {
git_dir: Some(git_dir.clone()),
..Default::default()
};
match gix::command::prepare(command.expose_secret())
.with_context(ctx)
.with_shell_allow_argument_splitting()
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
{
Ok(mut child) => match child.wait() {
Ok(_) => {
info!("Branch updated");
Ok(())
}
Err(err) => {
warn!(?err, ?git_dir, "Failed (wait)");
Err(push::Error::Push)
}
},
Err(err) => {
warn!(?err, ?git_dir, "Failed (spawn)");
Err(push::Error::Push)
}
}
}
}
}
#[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)] #[derive(Debug, Clone, derive_more::From, derive_more::Deref)]
pub struct Repository(gix::ThreadSafeRepository); // TODO: #[deprecated(note = "Use Repository::Real::open() or Repository::Real::clone()")]
impl Repository { pub struct LegacyRepository(gix::ThreadSafeRepository);
impl LegacyRepository {
pub fn open(gitdir: impl Into<PathBuf>) -> Result<Self, Error> { pub fn open(gitdir: impl Into<PathBuf>) -> Result<Self, Error> {
Ok(Self(gix::ThreadSafeRepository::open(gitdir.into())?)) Ok(Self(gix::ThreadSafeRepository::open(gitdir.into())?))
} }

View file

@ -1,16 +1,18 @@
use std::ops::Deref as _;
use git_next_config::{Hostname, RepoPath};
use gix::remote::Direction;
use tracing::info; use tracing::info;
use super::{GitRemote, RepoDetails, Repository}; use crate::repository::{open::OpenRepository, Direction};
use super::{GitRemote, RepoDetails};
#[tracing::instrument(skip_all)] #[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 git_remote = repo_details.git_remote();
let push_remote = find_default_remote(repository, Direction::Push)?; let Some(push_remote) = repository.find_default_remote(Direction::Push) else {
let fetch_remote = find_default_remote(repository, Direction::Fetch)?; 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"); info!(config = %git_remote, push = %push_remote, fetch = %fetch_remote, "Check remotes match");
if git_remote != push_remote { if git_remote != push_remote {
return Err(Error::MismatchDefaultPushRemote { return Err(Error::MismatchDefaultPushRemote {
@ -27,35 +29,11 @@ pub fn validate(repository: &Repository, repo_details: &RepoDetails) -> Result<(
Ok(()) 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>; type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, derive_more::Display)] #[derive(Debug, derive_more::Display)]
pub enum Error { pub enum Error {
NoDefaultPushRemote, NoDefaultPushRemote,
NoDefaultFetchRemote,
NoUrlForDefaultPushRemote, NoUrlForDefaultPushRemote,
NoHostnameForDefaultPushRemote, NoHostnameForDefaultPushRemote,
UnableToOpenRepo(String), UnableToOpenRepo(String),

View file

@ -18,7 +18,7 @@ pub async fn advance_next(
dev_commit_history: Vec<git::Commit>, dev_commit_history: Vec<git::Commit>,
repo_config: RepoConfig, repo_config: RepoConfig,
forge: gitforge::Forge, forge: gitforge::Forge,
repository: git::Repository, repository: git::OpenRepository,
addr: Addr<super::RepoActor>, addr: Addr<super::RepoActor>,
message_token: super::MessageToken, message_token: super::MessageToken,
) { ) {
@ -82,7 +82,7 @@ pub async fn advance_main(
next: git::Commit, next: git::Commit,
repo_config: RepoConfig, repo_config: RepoConfig,
forge: gitforge::Forge, forge: gitforge::Forge,
repository: git::Repository, repository: git::OpenRepository,
addr: Addr<RepoActor>, addr: Addr<RepoActor>,
message_token: super::MessageToken, message_token: super::MessageToken,
) { ) {

View file

@ -7,6 +7,7 @@ pub mod webhook;
mod tests; mod tests;
use actix::prelude::*; use actix::prelude::*;
use git::OpenRepository;
use git_next_config::{ForgeType, RepoConfig}; use git_next_config::{ForgeType, RepoConfig};
use git_next_git::{self as git, Generation, RepoDetails}; use git_next_git::{self as git, Generation, RepoDetails};
use kxio::network::Network; use kxio::network::Network;
@ -28,7 +29,7 @@ pub struct RepoActor {
last_main_commit: Option<git::Commit>, last_main_commit: Option<git::Commit>,
last_next_commit: Option<git::Commit>, last_next_commit: Option<git::Commit>,
last_dev_commit: Option<git::Commit>, last_dev_commit: Option<git::Commit>,
repository: Option<git::Repository>, repository: Option<OpenRepository>,
net: Network, net: Network,
forge: gitforge::Forge, forge: gitforge::Forge,
} }
@ -38,10 +39,11 @@ impl RepoActor {
webhook: Webhook, webhook: Webhook,
generation: Generation, generation: Generation,
net: Network, net: Network,
repo: git::Repository,
) -> Self { ) -> Self {
let forge = match details.forge.forge_type() { let forge = match details.forge.forge_type() {
#[cfg(feature = "forgejo")] #[cfg(feature = "forgejo")]
ForgeType::ForgeJo => gitforge::Forge::new_forgejo(details.clone(), net.clone()), ForgeType::ForgeJo => gitforge::Forge::new_forgejo(details.clone(), net.clone(), repo),
ForgeType::MockForge => gitforge::Forge::new_mock(), ForgeType::MockForge => gitforge::Forge::new_mock(),
}; };
debug!(?forge, "new"); debug!(?forge, "new");

View file

@ -3,7 +3,7 @@ use std::path::PathBuf;
use actix::prelude::*; use actix::prelude::*;
use git_next_config::{ForgeConfig, ForgeName, GitDir, RepoAlias, ServerRepoConfig}; use git_next_config::{ForgeConfig, ForgeName, GitDir, RepoAlias, ServerRepoConfig};
use git_next_git::{Generation, RepoDetails}; use git_next_git::{Generation, RepoDetails, Repository};
use kxio::{fs::FileSystem, network::Network}; use kxio::{fs::FileSystem, network::Network};
use tracing::{error, info, warn}; use tracing::{error, info, warn};
@ -37,6 +37,7 @@ pub struct Server {
webhook: Option<Addr<WebhookActor>>, webhook: Option<Addr<WebhookActor>>,
fs: FileSystem, fs: FileSystem,
net: Network, net: Network,
repo: Repository,
} }
impl Actor for Server { impl Actor for Server {
type Context = Context<Self>; type Context = Context<Self>;
@ -110,13 +111,14 @@ impl Handler<ServerConfig> for Server {
} }
} }
impl Server { impl Server {
pub fn new(fs: FileSystem, net: Network) -> Self { pub fn new(fs: FileSystem, net: Network, repo: Repository) -> Self {
let generation = Generation::new(); let generation = Generation::new();
Self { Self {
generation, generation,
webhook: None, webhook: None,
fs, fs,
net, net,
repo,
} }
} }
fn create_forge_data_directories( fn create_forge_data_directories(
@ -175,6 +177,7 @@ impl Server {
let server_storage = server_storage.clone(); let server_storage = server_storage.clone();
let webhook = webhook.clone(); let webhook = webhook.clone();
let net = self.net.clone(); let net = self.net.clone();
let repo = self.repo;
let generation = self.generation; let generation = self.generation;
move |(repo_alias, server_repo_config)| { move |(repo_alias, server_repo_config)| {
let span = tracing::info_span!("create_actor", alias = %repo_alias, config = %server_repo_config); let span = tracing::info_span!("create_actor", alias = %repo_alias, config = %server_repo_config);
@ -202,7 +205,8 @@ impl Server {
gitdir, gitdir,
); );
info!("Starting Repo Actor"); info!("Starting Repo Actor");
let actor = RepoActor::new(repo_details, webhook.clone(), generation, net.clone()); let actor =
RepoActor::new(repo_details, webhook.clone(), generation, net.clone(), repo);
(forge_name.clone(), repo_alias, actor) (forge_name.clone(), repo_alias, actor)
} }
} }

View file

@ -1,12 +1,12 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use assert2::let_assert; use assert2::let_assert;
use git::repository::Direction;
use git_next_config::{ use git_next_config::{
ForgeType, GitDir, Hostname, RepoBranches, RepoConfig, RepoConfigSource, RepoPath, ForgeType, GitDir, Hostname, RepoBranches, RepoConfig, RepoConfigSource, RepoPath,
ServerRepoConfig, ServerRepoConfig,
}; };
use git_next_git::{self as git, Generation, GitRemote, Repository}; use git_next_git::{self as git, Generation, GitRemote};
use gix::remote::Direction;
use kxio::fs; use kxio::fs;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@ -179,8 +179,11 @@ fn repo_details_find_default_push_remote_finds_correct_remote() -> Result<()> {
.with_hostname(Hostname::new("git.kemitix.net")); .with_hostname(Hostname::new("git.kemitix.net"));
repo_details.repo_path = RepoPath::new("kemitix/git-next".to_string()); repo_details.repo_path = RepoPath::new("kemitix/git-next".to_string());
let gitdir = &repo_details.gitdir; 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_assert!(
Some(found_git_remote) = repository.find_default_remote(Direction::Push),
"Default Push Remote not found"
);
let config_git_remote = repo_details.git_remote(); let config_git_remote = repo_details.git_remote();
assert_eq!( assert_eq!(
@ -207,7 +210,7 @@ fn gitdir_validate_should_pass_a_valid_git_repo() -> Result<()> {
.with_hostname(Hostname::new("git.kemitix.net")); .with_hostname(Hostname::new("git.kemitix.net"));
repo_details.repo_path = RepoPath::new("kemitix/git-next".to_string()); repo_details.repo_path = RepoPath::new("kemitix/git-next".to_string());
let gitdir = &repo_details.gitdir; let gitdir = &repo_details.gitdir;
let repository = Repository::open(gitdir)?; let repository = git::repository::new().open(gitdir)?;
git::validate(&repository, &repo_details)?; git::validate(&repository, &repo_details)?;
Ok(()) 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()); repo_details.repo_path = RepoPath::new("hello/world".to_string());
let gitdir = &repo_details.gitdir; 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)); let_assert!(Err(_) = git::validate(&repository, &repo_details));
Ok(()) Ok(())

View file

@ -28,7 +28,7 @@ pub struct ValidatedPositions {
pub async fn validate_positions( pub async fn validate_positions(
forge: &gitforge::forgejo::ForgeJoEnv, forge: &gitforge::forgejo::ForgeJoEnv,
repository: &git::Repository, repository: &git::OpenRepository,
repo_config: RepoConfig, repo_config: RepoConfig,
) -> Result<ValidatedPositions, Error> { ) -> Result<ValidatedPositions, Error> {
let repo_details = &forge.repo_details; let repo_details = &forge.repo_details;

View file

@ -5,6 +5,7 @@ use std::time::Duration;
use actix::prelude::*; use actix::prelude::*;
use git::OpenRepository;
use git_next_config::{BranchName, GitDir, RepoConfig}; 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, RepoDetails, Repository};
use kxio::network::{self, Network}; use kxio::network::{self, Network};
@ -21,10 +22,15 @@ struct ForgeJo;
pub struct ForgeJoEnv { pub struct ForgeJoEnv {
repo_details: RepoDetails, repo_details: RepoDetails,
net: Network, net: Network,
repo: Repository,
} }
impl ForgeJoEnv { impl ForgeJoEnv {
pub(super) const fn new(repo_details: RepoDetails, net: Network) -> Self { pub(super) const fn new(repo_details: RepoDetails, net: Network, repo: Repository) -> Self {
Self { repo_details, net } Self {
repo_details,
net,
repo,
}
} }
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -47,7 +53,7 @@ impl super::ForgeLike for ForgeJoEnv {
async fn branches_validate_positions( async fn branches_validate_positions(
&self, &self,
repository: git::Repository, repository: git::OpenRepository,
repo_config: RepoConfig, repo_config: RepoConfig,
addr: Addr<RepoActor>, addr: Addr<RepoActor>,
message_token: MessageToken, message_token: MessageToken,
@ -71,19 +77,13 @@ impl super::ForgeLike for ForgeJoEnv {
fn branch_reset( fn branch_reset(
&self, &self,
repository: &git::Repository, repository: &git::OpenRepository,
branch_name: BranchName, branch_name: BranchName,
to_commit: GitRef, to_commit: GitRef,
force: git::push::Force, force: git::push::Force,
) -> git::push::Result { ) -> git::push::Result {
git::fetch(repository, &self.repo_details)?; repository.fetch()?;
git::push( repository.push(&self.repo_details, branch_name, to_commit, force)
repository,
&self.repo_details,
branch_name,
to_commit,
force,
)
} }
async fn commit_status(&self, commit: &git::Commit) -> gitforge::CommitStatus { async fn commit_status(&self, commit: &git::Commit) -> gitforge::CommitStatus {
@ -130,12 +130,12 @@ impl super::ForgeLike for ForgeJoEnv {
} }
} }
fn repo_clone(&self, gitdir: GitDir) -> Result<Repository, git::repository::Error> { fn repo_clone(&self, gitdir: GitDir) -> Result<OpenRepository, git::repository::Error> {
let repository = if !gitdir.exists() { let repository = if !gitdir.exists() {
info!("Local copy not found - cloning..."); info!("Local copy not found - cloning...");
Repository::clone(&self.repo_details)? self.repo.git_clone(&self.repo_details)?
} else { } else {
Repository::open(gitdir)? self.repo.open(&gitdir)?
}; };
info!("Validating..."); info!("Validating...");
git::validate(&repository, &self.repo_details) git::validate(&repository, &self.repo_details)

View file

@ -1,5 +1,6 @@
use git::OpenRepository;
use git_next_config::{BranchName, GitDir, RepoConfig}; use git_next_config::{BranchName, GitDir, RepoConfig};
use git_next_git::{self as git, GitRef, Repository}; use git_next_git::{self as git, GitRef};
use crate::{actors::repo::RepoActor, gitforge, types::MessageToken}; use crate::{actors::repo::RepoActor, gitforge, types::MessageToken};
@ -31,7 +32,7 @@ impl super::ForgeLike for MockForgeEnv {
async fn branches_validate_positions( async fn branches_validate_positions(
&self, &self,
_repository: Repository, _repository: OpenRepository,
_repo_config: RepoConfig, _repo_config: RepoConfig,
_addr: actix::prelude::Addr<RepoActor>, _addr: actix::prelude::Addr<RepoActor>,
_message_token: MessageToken, _message_token: MessageToken,
@ -41,7 +42,7 @@ impl super::ForgeLike for MockForgeEnv {
fn branch_reset( fn branch_reset(
&self, &self,
_repository: &Repository, _repository: &OpenRepository,
_branch_name: BranchName, _branch_name: BranchName,
_to_commit: GitRef, _to_commit: GitRef,
_force: git::push::Force, _force: git::push::Force,
@ -53,7 +54,7 @@ impl super::ForgeLike for MockForgeEnv {
todo!() todo!()
} }
fn repo_clone(&self, _gitdir: GitDir) -> Result<Repository, git::repository::Error> { fn repo_clone(&self, _gitdir: GitDir) -> Result<OpenRepository, git::repository::Error> {
todo!() todo!()
} }
} }

View file

@ -1,7 +1,8 @@
#![allow(dead_code)] #![allow(dead_code)]
use git::OpenRepository;
use git_next_config::{BranchName, GitDir, RepoConfig}; 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, RepoDetails};
use kxio::network::Network; use kxio::network::Network;
#[cfg(feature = "forgejo")] #[cfg(feature = "forgejo")]
@ -38,7 +39,7 @@ pub trait ForgeLike {
/// positions as needed. /// positions as needed.
async fn branches_validate_positions( async fn branches_validate_positions(
&self, &self,
repository: Repository, repository: OpenRepository,
repo_config: RepoConfig, repo_config: RepoConfig,
addr: actix::prelude::Addr<super::actors::repo::RepoActor>, addr: actix::prelude::Addr<super::actors::repo::RepoActor>,
message_token: MessageToken, message_token: MessageToken,
@ -47,7 +48,7 @@ pub trait ForgeLike {
/// Moves a branch to a new commit. /// Moves a branch to a new commit.
fn branch_reset( fn branch_reset(
&self, &self,
repository: &Repository, repository: &OpenRepository,
branch_name: BranchName, branch_name: BranchName,
to_commit: GitRef, to_commit: GitRef,
force: git::push::Force, force: git::push::Force,
@ -57,7 +58,7 @@ pub trait ForgeLike {
async fn commit_status(&self, commit: &git::Commit) -> CommitStatus; async fn commit_status(&self, commit: &git::Commit) -> CommitStatus;
/// Clones a repo to disk. /// Clones a repo to disk.
fn repo_clone(&self, gitdir: GitDir) -> Result<Repository, git::repository::Error>; fn repo_clone(&self, gitdir: GitDir) -> Result<OpenRepository, git::repository::Error>;
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -74,8 +75,12 @@ impl Forge {
Self::Mock(mock_forge::MockForgeEnv::new()) Self::Mock(mock_forge::MockForgeEnv::new())
} }
#[cfg(feature = "forgejo")] #[cfg(feature = "forgejo")]
pub const fn new_forgejo(repo_details: RepoDetails, net: Network) -> Self { pub const fn new_forgejo(
Self::ForgeJo(forgejo::ForgeJoEnv::new(repo_details, net)) repo_details: RepoDetails,
net: Network,
repo: git::Repository,
) -> Self {
Self::ForgeJo(forgejo::ForgeJoEnv::new(repo_details, net, repo))
} }
#[cfg(feature = "github")] #[cfg(feature = "github")]
pub const fn new_github(net: Network) -> Self { pub const fn new_github(net: Network) -> Self {

View file

@ -13,6 +13,7 @@ fn test_name() {
panic!("fs") panic!("fs")
}; };
let net = Network::new_mock(); let net = Network::new_mock();
let repo = git::repository::mock();
let repo_details = common::repo_details( let repo_details = common::repo_details(
1, 1,
Generation::new(), Generation::new(),
@ -20,7 +21,7 @@ fn test_name() {
Some(common::repo_config(1, RepoConfigSource::Repo)), Some(common::repo_config(1, RepoConfigSource::Repo)),
GitDir::new(fs.base()), GitDir::new(fs.base()),
); );
let forge = Forge::new_forgejo(repo_details, net); let forge = Forge::new_forgejo(repo_details, net, repo);
assert_eq!(forge.name(), "forgejo"); assert_eq!(forge.name(), "forgejo");
} }
@ -39,6 +40,7 @@ async fn test_branches_get() {
let body = include_str!("./data-forgejo-branches-get.json"); let body = include_str!("./data-forgejo-branches-get.json");
net.add_get_response(&url, StatusCode::OK, body); net.add_get_response(&url, StatusCode::OK, body);
let net = Network::from(net); let net = Network::from(net);
let repo = git::repository::mock();
let repo_details = common::repo_details( let repo_details = common::repo_details(
1, 1,
@ -48,7 +50,7 @@ async fn test_branches_get() {
GitDir::new(fs.base()), GitDir::new(fs.base()),
); );
let forge = Forge::new_forgejo(repo_details, net.clone()); let forge = Forge::new_forgejo(repo_details, net.clone(), repo);
let_assert!(Ok(branches) = forge.branches_get_all().await); let_assert!(Ok(branches) = forge.branches_get_all().await);

View file

@ -5,6 +5,7 @@ pub mod types;
use actix::prelude::*; use actix::prelude::*;
use git_next_git::Repository;
use kxio::{fs::FileSystem, network::Network}; use kxio::{fs::FileSystem, network::Network};
use std::path::PathBuf; use std::path::PathBuf;
@ -38,11 +39,11 @@ pub fn init(fs: FileSystem) {
} }
} }
pub async fn start(fs: FileSystem, net: Network) { pub async fn start(fs: FileSystem, net: Network, repo: Repository) {
init_logging(); init_logging();
info!("Starting Server..."); info!("Starting Server...");
let server = Server::new(fs.clone(), net.clone()).start(); let server = Server::new(fs.clone(), net.clone(), repo).start();
server.do_send(FileUpdated); server.do_send(FileUpdated);
info!("Starting File Watcher..."); info!("Starting File Watcher...");