mod branch; mod config; pub mod status; pub mod webhook; use actix::prelude::*; use kxio::network::Network; use tracing::{info, warn}; use crate::server::{ actors::repo::webhook::WebhookAuth, config::{RepoConfig, RepoDetails, Webhook}, gitforge, }; use self::webhook::WebhookId; pub struct RepoActor { details: RepoDetails, webhook: Webhook, webhook_id: Option, // INFO: if [None] then no webhook is configured webhook_auth: Option, // INFO: if [None] then no webhook is configured last_main_commit: Option, last_next_commit: Option, last_dev_commit: Option, net: Network, forge: gitforge::Forge, } impl RepoActor { pub(crate) fn new(details: RepoDetails, webhook: Webhook, net: Network) -> Self { let forge = match details.forge.forge_type { #[cfg(feature = "forgejo")] crate::server::config::ForgeType::ForgeJo => { gitforge::Forge::new_forgejo(details.clone(), net.clone()) } #[cfg(test)] crate::server::config::ForgeType::MockForge => gitforge::Forge::new_mock(), }; Self { details, webhook, webhook_id: None, webhook_auth: None, last_main_commit: None, last_next_commit: None, last_dev_commit: None, net, forge, } } } impl Actor for RepoActor { type Context = Context; fn stopping(&mut self, ctx: &mut Self::Context) -> Running { match self.webhook_id.take() { Some(webhook_id) => { let repo_details = self.details.clone(); let net = self.net.clone(); webhook::unregister(webhook_id, repo_details, net) .into_actor(self) .wait(ctx); Running::Continue } None => Running::Stop, } } } impl std::fmt::Display for RepoActor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "RepoActor: {}/{}", self.details.forge.forge_name, self.details.repo_alias ) } } #[derive(Message)] #[rtype(result = "()")] pub struct CloneRepo; impl Handler for RepoActor { type Result = (); #[tracing::instrument(skip_all, fields(%self))] fn handle(&mut self, _msg: CloneRepo, ctx: &mut Self::Context) -> Self::Result { info!(%self.details, "Clone/Update Repo"); let gitdir = self.details.gitdir.clone(); match self.forge.repo_clone(gitdir) { Ok(_) => ctx.address().do_send(LoadConfigFromRepo), Err(err) => warn!("Could not Clone repo: {err}"), } } } #[derive(Message)] #[rtype(result = "()")] pub struct LoadConfigFromRepo; impl Handler for RepoActor { type Result = (); #[tracing::instrument(skip_all, fields(%self))] fn handle(&mut self, _msg: LoadConfigFromRepo, ctx: &mut Self::Context) -> Self::Result { info!(%self.details, "Loading .git-next.toml from repo"); let details = self.details.clone(); let addr = ctx.address(); let forge = self.forge.clone(); config::load(details, addr, forge) .into_actor(self) .wait(ctx); } } #[derive(Message)] #[rtype(result = "()")] struct LoadedConfig(pub RepoConfig); impl Handler for RepoActor { type Result = (); #[tracing::instrument(skip_all, fields(%self))] fn handle(&mut self, msg: LoadedConfig, ctx: &mut Self::Context) -> Self::Result { let repo_config = msg.0; info!(%self.details, %repo_config, "Config loaded"); self.details.repo_config.replace(repo_config); if self.webhook_id.is_none() { webhook::register( self.details.clone(), self.webhook.clone(), ctx.address(), self.net.clone(), ) .into_actor(self) .wait(ctx); } ctx.address().do_send(ValidateRepo); } } #[derive(Message)] #[rtype(result = "()")] pub struct ValidateRepo; impl Handler for RepoActor { type Result = (); #[tracing::instrument(skip_all, fields(%self))] fn handle(&mut self, _msg: ValidateRepo, ctx: &mut Self::Context) -> Self::Result { info!("ValidateRepo"); if let Some(repo_config) = self.details.repo_config.clone() { let forge = self.forge.clone(); let addr = ctx.address(); async move { forge.branches_validate_positions(repo_config, addr).await } .into_actor(self) .wait(ctx); } } } #[derive(Debug, Message)] #[rtype(result = "()")] pub struct StartMonitoring { pub main: gitforge::Commit, pub next: gitforge::Commit, pub dev: gitforge::Commit, pub dev_commit_history: Vec, } impl Handler for RepoActor { type Result = (); #[tracing::instrument(skip_all, fields(%self))] fn handle(&mut self, msg: StartMonitoring, ctx: &mut Self::Context) -> Self::Result { info!("StartMonitoring"); let Some(repo_config) = self.details.repo_config.clone() else { warn!("No config loaded"); return; }; let next_ahead_of_main = msg.main != msg.next; let dev_ahead_of_next = msg.next != msg.dev; info!(%msg.main, %msg.next, %msg.dev, next_ahead_of_main, dev_ahead_of_next, "StartMonitoring"); let repo_details = self.details.clone(); let webhook = self.webhook.clone(); let addr = ctx.address(); let net = self.net.clone(); let forge = self.forge.clone(); if next_ahead_of_main { status::check_next(msg.next, addr, forge) .into_actor(self) .wait(ctx); } else if dev_ahead_of_next { branch::advance_next(msg.next, msg.dev_commit_history, repo_config, forge, addr) .into_actor(self) .wait(ctx); } else if self.webhook_id.is_none() { webhook::register(repo_details, webhook, addr, net) .into_actor(self) .wait(ctx); } } } #[derive(Message)] #[rtype(result = "()")] pub struct WebhookRegistered(pub WebhookId, pub WebhookAuth); impl Handler for RepoActor { type Result = (); #[tracing::instrument(skip_all, fields(%self))] fn handle(&mut self, msg: WebhookRegistered, _ctx: &mut Self::Context) -> Self::Result { self.webhook_id.replace(msg.0); self.webhook_auth.replace(msg.1); } } #[derive(Message)] #[rtype(result = "()")] pub struct AdvanceMainTo(pub gitforge::Commit); impl Handler for RepoActor { type Result = (); #[tracing::instrument(skip_all, fields(%self))] fn handle(&mut self, msg: AdvanceMainTo, ctx: &mut Self::Context) -> Self::Result { let Some(repo_config) = self.details.repo_config.clone() else { warn!("No config loaded"); return; }; let forge = self.forge.clone(); let addr = ctx.address(); branch::advance_main(msg.0, repo_config, forge, addr) .into_actor(self) .wait(ctx); } }