gitdataai/libs/service/git/refs.rs
2026-04-15 09:08:09 +08:00

357 lines
11 KiB
Rust

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<String>,
}
#[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<String>,
}
#[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<String>,
pub message: Option<String>,
}
#[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<String>,
pub target: Option<String>,
pub is_symbolic: bool,
pub is_branch: bool,
pub is_remote: bool,
pub is_tag: bool,
pub is_note: bool,
}
impl From<RefInfo> 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<String>,
}
#[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<String>,
pub new_oid: Option<String>,
}
impl AppService {
pub async fn git_ref_list(
&self,
namespace: String,
repo_name: String,
query: RefListQuery,
ctx: &Session,
) -> Result<Vec<RefInfoResponse>, 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<RefInfoResponse> = refs.into_iter().map(RefInfoResponse::from).collect();
if let Ok(mut conn) = self.cache.conn().await {
if let Err(e) = conn
.set_ex::<String, String, ()>(
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<RefInfoResponse, AppError> {
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<RefUpdateResponse, AppError> {
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::<String, ()>(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<RefDeleteResponse, AppError> {
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::<String, ()>(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<RefInfoResponse, AppError> {
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::<String, ()>(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<RefUpdateResponse, AppError> {
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<RefExistsResponse, AppError> {
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<RefTargetResponse, AppError> {
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()),
})
}
}