use crate::AppService; use crate::error::AppError; use crate::git::RefInfo; use models::repos::repo; use redis::AsyncCommands; use serde::{Deserialize, Serialize}; use session::Session; #[derive(Debug, Clone, Deserialize, utoipa::IntoParams)] pub struct RefListQuery { pub pattern: Option, } #[derive(Debug, Clone, Deserialize)] pub struct RefGetQuery { pub name: String, } #[derive(Debug, Clone, Deserialize, utoipa::ToSchema)] pub struct RefCreateRequest { pub name: String, pub oid: String, #[serde(default)] pub force: bool, pub message: Option, } #[derive(Debug, Clone, Deserialize)] pub struct RefDeleteQuery { pub name: String, } #[derive(Debug, Clone, Deserialize, utoipa::IntoParams)] pub struct RefRenameQuery { pub old_name: String, pub new_name: String, #[serde(default)] pub force: bool, } #[derive(Debug, Clone, Deserialize, utoipa::ToSchema)] pub struct RefUpdateRequest { pub name: String, pub new_oid: String, pub expected_oid: Option, pub message: Option, } #[derive(Debug, Clone, Deserialize)] pub struct RefTargetQuery { pub name: String, } #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct RefInfoResponse { pub name: String, pub oid: Option, pub target: Option, pub is_symbolic: bool, pub is_branch: bool, pub is_remote: bool, pub is_tag: bool, pub is_note: bool, } impl From for RefInfoResponse { fn from(r: RefInfo) -> Self { Self { name: r.name, oid: r.oid.map(|o| o.to_string()), target: r.target.map(|t| t.to_string()), is_symbolic: r.is_symbolic, is_branch: r.is_branch, is_remote: r.is_remote, is_tag: r.is_tag, is_note: r.is_note, } } } #[derive(Debug, Clone, Serialize, utoipa::ToSchema)] pub struct RefExistsResponse { pub name: String, pub exists: bool, } #[derive(Debug, Clone, Serialize, utoipa::ToSchema)] pub struct RefTargetResponse { pub name: String, pub oid: Option, } #[derive(Debug, Clone, Serialize, utoipa::ToSchema)] pub struct RefDeleteResponse { pub name: String, pub oid: String, } #[derive(Debug, Clone, Serialize, utoipa::ToSchema)] pub struct RefUpdateResponse { pub name: String, pub old_oid: Option, pub new_oid: Option, } impl AppService { pub async fn git_ref_list( &self, namespace: String, repo_name: String, query: RefListQuery, ctx: &Session, ) -> Result, AppError> { let repo = self .utils_find_repo(namespace.clone(), repo_name.clone(), ctx) .await?; let cache_key = format!( "git:ref:list:{}:{}:{}", namespace, repo_name, query.pattern.as_deref().unwrap_or("*") ); if let Ok(mut conn) = self.cache.conn().await { if let Ok(cached) = conn.get::<_, String>(cache_key.clone()).await { if let Ok(cached) = serde_json::from_str(&cached) { return Ok(cached); } } } let repo_clone = repo.clone(); let pattern_clone = query.pattern.clone(); let refs = tokio::task::spawn_blocking(move || { let domain = git::GitDomain::from_model(repo_clone)?; domain.ref_list(pattern_clone.as_deref()) }) .await .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(AppError::from)?; let response: Vec = refs.into_iter().map(RefInfoResponse::from).collect(); if let Ok(mut conn) = self.cache.conn().await { if let Err(e) = conn .set_ex::( cache_key, serde_json::to_string(&response).unwrap_or_default(), 60 * 60, ) .await { slog::debug!(self.logs, "cache set failed (non-fatal): {}", e); } } Ok(response) } pub async fn git_ref_get( &self, namespace: String, repo_name: String, name: String, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let info = tokio::task::spawn_blocking(move || { let domain = git::GitDomain::from_model(repo)?; domain.ref_get(&name) }) .await .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(AppError::from)?; Ok(RefInfoResponse::from(info)) } pub async fn git_ref_create( &self, namespace: String, repo_name: String, request: RefCreateRequest, ctx: &Session, ) -> Result { let repo: repo::Model = self .utils_check_repo_admin(namespace.clone(), repo_name.clone(), ctx) .await?; let name = request.name.clone(); let oid = git::CommitOid::new(&request.oid); let force = request.force; let message = request.message.clone(); let result = tokio::task::spawn_blocking(move || { let domain = git::GitDomain::from_model(repo)?; domain.ref_create(&name, oid, force, message.as_deref()) }) .await .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(AppError::from)?; if let Ok(mut conn) = self.cache.conn().await { let key = format!("git:ref:list:{}:{}:*", namespace, repo_name); if let Err(e) = conn.del::(key).await { slog::debug!(self.logs, "cache del failed (non-fatal): {}", e); } } Ok(RefUpdateResponse { name: result.name, old_oid: result.old_oid.map(|o| o.to_string()), new_oid: result.new_oid.map(|o| o.to_string()), }) } pub async fn git_ref_delete( &self, namespace: String, repo_name: String, name: String, ctx: &Session, ) -> Result { let repo: repo::Model = self .utils_check_repo_admin(namespace.clone(), repo_name.clone(), ctx) .await?; let name_clone = name.clone(); let oid = tokio::task::spawn_blocking(move || { let domain = git::GitDomain::from_model(repo)?; domain.ref_delete(&name_clone) }) .await .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(AppError::from)?; if let Ok(mut conn) = self.cache.conn().await { let key = format!("git:ref:list:{}:{}:*", namespace, repo_name); if let Err(e) = conn.del::(key).await { slog::debug!(self.logs, "cache del failed (non-fatal): {}", e); } } Ok(RefDeleteResponse { name, oid: oid.to_string(), }) } pub async fn git_ref_rename( &self, namespace: String, repo_name: String, query: RefRenameQuery, ctx: &Session, ) -> Result { let repo: repo::Model = self .utils_check_repo_admin(namespace.clone(), repo_name.clone(), ctx) .await?; let old_name = query.old_name.clone(); let new_name = query.new_name.clone(); let force = query.force; let info = tokio::task::spawn_blocking(move || { let domain = git::GitDomain::from_model(repo)?; domain.ref_rename(&old_name, &new_name, force) }) .await .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(AppError::from)?; if let Ok(mut conn) = self.cache.conn().await { let key = format!("git:ref:list:{}:{}:*", namespace, repo_name); if let Err(e) = conn.del::(key).await { slog::debug!(self.logs, "cache del failed (non-fatal): {}", e); } } Ok(RefInfoResponse::from(info)) } pub async fn git_ref_update( &self, namespace: String, repo_name: String, request: RefUpdateRequest, ctx: &Session, ) -> Result { let repo: repo::Model = self .utils_check_repo_admin(namespace, repo_name, ctx) .await?; let name = request.name.clone(); let new_oid = git::CommitOid::new(&request.new_oid); let expected_oid = request.expected_oid.map(|o| git::CommitOid::new(&o)); let message = request.message.clone(); let result = tokio::task::spawn_blocking(move || { let domain = git::GitDomain::from_model(repo)?; domain.ref_update(&name, new_oid, expected_oid, message.as_deref()) }) .await .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(AppError::from)?; Ok(RefUpdateResponse { name: result.name, old_oid: result.old_oid.map(|o| o.to_string()), new_oid: result.new_oid.map(|o| o.to_string()), }) } pub async fn git_ref_exists( &self, namespace: String, repo_name: String, name: String, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let name_clone = name.clone(); let exists = tokio::task::spawn_blocking(move || { let domain = git::GitDomain::from_model(repo)?; Ok::<_, git::GitError>(domain.ref_exists(&name_clone)) }) .await .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(AppError::from)?; Ok(RefExistsResponse { name, exists }) } pub async fn git_ref_target( &self, namespace: String, repo_name: String, name: String, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let name_clone = name.clone(); let oid = tokio::task::spawn_blocking(move || { let domain = git::GitDomain::from_model(repo)?; domain.ref_target(&name_clone) }) .await .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(AppError::from)?; Ok(RefTargetResponse { name, oid: oid.map(|o| o.to_string()), }) } }