357 lines
11 KiB
Rust
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()),
|
|
})
|
|
}
|
|
}
|