use config::AppConfig; use db::cache::AppCache; use db::database::AppDatabase; use deadpool_redis::cluster::Pool as RedisPool; use models::EntityTrait; use slog::Logger; use std::sync::Arc; /// Simplified hook service — no queue, no pool. /// K8s StatefulSet HA scheduling ensures only one pod touches a repo at a time. /// Execution is direct and sequential per invocation. #[derive(Clone)] pub struct HookService { pub(crate) db: AppDatabase, pub(crate) cache: AppCache, pub(crate) redis_pool: RedisPool, pub(crate) logger: Logger, pub(crate) config: AppConfig, pub(crate) http: Arc, } impl HookService { pub fn new( db: AppDatabase, cache: AppCache, redis_pool: RedisPool, logger: Logger, config: AppConfig, http: Arc, ) -> Self { Self { db, cache, redis_pool, logger, config, http, } } /// Full sync: refs → commits → tags → LFS → fsck → gc → skills. pub async fn sync_repo(&self, repo_id: &str) -> Result<(), crate::GitError> { let repo_uuid = models::Uuid::parse_str(repo_id) .map_err(|_| crate::GitError::Internal("invalid repo_id uuid".into()))?; let repo = models::repos::repo::Entity::find_by_id(repo_uuid) .one(self.db.reader()) .await .map_err(crate::GitError::from)? .ok_or_else(|| crate::GitError::NotFound(format!("repo {} not found", repo_id)))?; if !std::path::Path::new(&repo.storage_path).exists() { return Err(crate::GitError::NotFound(format!( "storage path does not exist: {}", repo.storage_path ))); } let sync = crate::hook::sync::HookMetaDataSync::new( self.db.clone(), self.cache.clone(), repo, self.logger.clone(), )?; // No distributed lock needed — K8s StatefulSet scheduling guarantees // that at most one pod processes a given repo shard at any time. sync.sync().await } /// Run fsck only (no full sync). pub async fn fsck_repo(&self, repo_id: &str) -> Result<(), crate::GitError> { let repo_uuid = models::Uuid::parse_str(repo_id) .map_err(|_| crate::GitError::Internal("invalid repo_id uuid".into()))?; let repo = models::repos::repo::Entity::find_by_id(repo_uuid) .one(self.db.reader()) .await .map_err(crate::GitError::from)? .ok_or_else(|| crate::GitError::NotFound(format!("repo {} not found", repo_id)))?; if !std::path::Path::new(&repo.storage_path).exists() { return Err(crate::GitError::NotFound(format!( "storage path does not exist: {}", repo.storage_path ))); } let sync = crate::hook::sync::HookMetaDataSync::new( self.db.clone(), self.cache.clone(), repo, self.logger.clone(), )?; sync.fsck_only().await } /// Run gc only (no full sync). pub async fn gc_repo(&self, repo_id: &str) -> Result<(), crate::GitError> { let repo_uuid = models::Uuid::parse_str(repo_id) .map_err(|_| crate::GitError::Internal("invalid repo_id uuid".into()))?; let repo = models::repos::repo::Entity::find_by_id(repo_uuid) .one(self.db.reader()) .await .map_err(crate::GitError::from)? .ok_or_else(|| crate::GitError::NotFound(format!("repo {} not found", repo_id)))?; if !std::path::Path::new(&repo.storage_path).exists() { return Err(crate::GitError::NotFound(format!( "storage path does not exist: {}", repo.storage_path ))); } let sync = crate::hook::sync::HookMetaDataSync::new( self.db.clone(), self.cache.clone(), repo, self.logger.clone(), )?; sync.gc_only().await } } pub mod sync; pub mod webhook_dispatch;