pub mod branch; mod file; use git::OpenRepository; use git_next_config::{BranchName, GitDir, RepoConfig}; use git_next_git::{self as git, GitRef, RepoDetails, Repository}; use kxio::network::{self, Network}; use tracing::{error, info, warn}; use crate::{validation, CommitStatus}; struct ForgeJo; #[derive(Clone, Debug)] pub struct ForgeJoEnv { repo_details: RepoDetails, net: Network, repo: Repository, } impl ForgeJoEnv { pub(super) const fn new(repo_details: RepoDetails, net: Network, repo: Repository) -> Self { Self { repo_details, net, repo, } } } #[async_trait::async_trait] impl super::ForgeLike for ForgeJoEnv { fn name(&self) -> String { "forgejo".to_string() } async fn branches_get_all(&self) -> Result, crate::branch::Error> { 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: git::OpenRepository, repo_config: RepoConfig, ) -> validation::Result { branch::validate_positions(self, &repository, repo_config).await } fn branch_reset( &self, repository: &git::OpenRepository, branch_name: BranchName, to_commit: GitRef, force: git::push::Force, ) -> git::push::Result { repository.fetch()?; repository.push(&self.repo_details, branch_name, to_commit, force) } async fn commit_status(&self, commit: &git::Commit) -> 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 => CommitStatus::Pass, CommitStatusState::Pending => CommitStatus::Pending, CommitStatusState::Failure => CommitStatus::Fail, CommitStatusState::Error => CommitStatus::Fail, CommitStatusState::Blank => CommitStatus::Pending, }, None => { warn!("No status found for commit"); CommitStatus::Pending // assume issue is transient and allow retry } } } Err(e) => { error!(?e, "Failed to get commit status"); 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..."); self.repo.git_clone(&self.repo_details)? } else { self.repo.open(&gitdir)? }; info!("Validating..."); git::validate(&repository, &self.repo_details) .map_err(|e| git::repository::Error::Validation(e.to_string()))?; 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, }