pub mod branch; mod file; mod repo; use std::time::Duration; use actix::prelude::*; use kxio::network::{self, Network}; use tracing::{error, info, warn}; use crate::server::{ actors::repo::{RepoActor, StartMonitoring, ValidateRepo}, config::{BranchName, GitDir, RepoConfig, RepoDetails}, git, gitforge::{self, forgejo::branch::ValidatedPositions, RepoCloneError, Repository}, types::{GitRef, MessageToken}, }; struct ForgeJo; #[derive(Clone, Debug)] pub struct ForgeJoEnv { repo_details: RepoDetails, net: Network, } impl ForgeJoEnv { pub(super) const fn new(repo_details: RepoDetails, net: Network) -> Self { Self { repo_details, net } } } #[async_trait::async_trait] impl super::ForgeLike for ForgeJoEnv { fn name(&self) -> String { "forgejo".to_string() } async fn branches_get_all(&self) -> Result, gitforge::ForgeBranchError> { branch::get_all(&self.repo_details, &self.net).await } async fn file_contents_get( &self, branch: &BranchName, file_path: &str, ) -> Result { file::contents_get(&self.repo_details, &self.net, branch, file_path).await } async fn branches_validate_positions( &self, repository: Repository, repo_config: RepoConfig, addr: Addr, message_token: MessageToken, ) { match branch::validate_positions(self, &repository, repo_config).await { Ok(ValidatedPositions { main, next, dev, dev_commit_history, }) => { addr.do_send(StartMonitoring { main, next, dev, dev_commit_history, }); } Err(err) => { warn!("{}", err); tokio::time::sleep(Duration::from_secs(10)).await; addr.do_send(ValidateRepo::new(message_token)); } } } fn branch_reset( &self, repository: &Repository, branch_name: BranchName, to_commit: GitRef, force: gitforge::Force, ) -> gitforge::BranchResetResult { branch::fetch(repository, &self.repo_details)?; git::reset( repository, &self.repo_details, branch_name, to_commit, force, ) } async fn commit_status(&self, commit: &gitforge::Commit) -> gitforge::CommitStatus { let repo_details = &self.repo_details; let hostname = &repo_details.forge.hostname; let repo_path = &repo_details.repo_path; let api_token = &repo_details.forge.token; use secrecy::ExposeSecret; let token = api_token.expose_secret(); let url = network::NetUrl::new(format!( "https://{hostname}/api/v1/repos/{repo_path}/commits/{commit}/status?token={token}" )); let request = network::NetRequest::new( network::RequestMethod::Get, url, network::NetRequestHeaders::new(), network::RequestBody::None, network::ResponseType::Json, None, network::NetRequestLogging::None, ); let result = self.net.get::(request).await; match result { Ok(response) => { match response.response_body() { Some(status) => match status.state { CommitStatusState::Success => gitforge::CommitStatus::Pass, CommitStatusState::Pending => gitforge::CommitStatus::Pending, CommitStatusState::Failure => gitforge::CommitStatus::Fail, CommitStatusState::Error => gitforge::CommitStatus::Fail, CommitStatusState::Blank => gitforge::CommitStatus::Pending, }, None => { warn!("No status found for commit"); gitforge::CommitStatus::Pending // assume issue is transient and allow retry } } } Err(e) => { error!(?e, "Failed to get commit status"); gitforge::CommitStatus::Pending // assume issue is transient and allow retry } } } fn repo_clone(&self, gitdir: GitDir) -> Result { let repository = if !gitdir.exists() { info!("Local copy not found - cloning..."); repo::clone(&self.repo_details, gitdir.clone())? } else { repo::open(gitdir.clone())? }; info!("Validating..."); gitdir .validate(&repository, &self.repo_details) .map_err(|e| RepoCloneError::Validation(e.to_string())) .inspect(|_| info!("Validation - OK"))?; Ok(repository) } } #[derive(Debug, serde::Deserialize)] pub struct CombinedStatus { pub state: CommitStatusState, } #[derive(Debug, serde::Deserialize)] pub enum CommitStatusState { #[serde(rename = "success")] Success, #[serde(rename = "pending")] Pending, #[serde(rename = "failure")] Failure, #[serde(rename = "error")] Error, #[serde(rename = "")] Blank, }