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

352 lines
10 KiB
Rust

use crate::AppService;
use crate::error::AppError;
use crate::git::{TreeEntry, TreeInfo};
use redis::AsyncCommands;
use serde::{Deserialize, Serialize};
use session::Session;
#[derive(Debug, Clone, Deserialize)]
pub struct TreeGetQuery {
#[serde(default)]
pub oid: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TreeEntryQuery {
#[serde(default)]
pub oid: String,
pub index: usize,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TreeEntryByPathQuery {
#[serde(default)]
pub oid: String,
pub path: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TreeEntryByCommitPathQuery {
pub commit: String,
pub path: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TreeDiffQuery {
pub old_tree: String,
pub new_tree: String,
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct TreeInfoResponse {
pub oid: String,
pub entry_count: usize,
pub is_empty: bool,
}
impl From<TreeInfo> for TreeInfoResponse {
fn from(t: TreeInfo) -> Self {
Self {
oid: t.oid.to_string(),
entry_count: t.entry_count,
is_empty: t.is_empty,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct TreeEntryResponse {
pub name: String,
pub oid: String,
pub kind: String,
pub filemode: u32,
pub is_binary: bool,
}
impl From<TreeEntry> for TreeEntryResponse {
fn from(e: TreeEntry) -> Self {
Self {
name: e.name,
oid: e.oid.to_string(),
kind: e.kind,
filemode: e.filemode,
is_binary: e.is_binary,
}
}
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct TreeExistsResponse {
pub oid: String,
pub exists: bool,
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct TreeEntryCountResponse {
pub oid: String,
pub count: usize,
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct TreeIsEmptyResponse {
pub oid: String,
pub is_empty: bool,
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct TreeDiffStatsResponse {
pub old_tree: String,
pub new_tree: String,
pub files_changed: usize,
pub insertions: usize,
pub deletions: usize,
}
impl AppService {
pub async fn git_tree_get(
&self,
namespace: String,
repo_name: String,
query: TreeGetQuery,
ctx: &Session,
) -> Result<TreeInfoResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let info = tokio::task::spawn_blocking(move || {
let domain = git::GitDomain::from_model(repo)?;
let oid = git::CommitOid::new(&oid_str);
domain.tree_get(&oid)
})
.await
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?;
Ok(TreeInfoResponse::from(info))
}
pub async fn git_tree_exists(
&self,
namespace: String,
repo_name: String,
query: TreeGetQuery,
ctx: &Session,
) -> Result<TreeExistsResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let exists = tokio::task::spawn_blocking(move || {
let domain = git::GitDomain::from_model(repo)?;
let oid = git::CommitOid::new(&oid_str);
Ok::<_, git::GitError>(domain.tree_exists(&oid))
})
.await
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?;
Ok(TreeExistsResponse {
oid: query.oid,
exists,
})
}
pub async fn git_tree_list(
&self,
namespace: String,
repo_name: String,
query: TreeGetQuery,
ctx: &Session,
) -> Result<Vec<TreeEntryResponse>, AppError> {
let repo = self
.utils_find_repo(namespace.clone(), repo_name.clone(), ctx)
.await?;
let cache_key = format!("git:tree:list:{}:{}:{}", namespace, repo_name, query.oid);
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 oid_str = query.oid.clone();
let entries = tokio::task::spawn_blocking(move || {
let domain = git::GitDomain::from_model(repo)?;
let oid = git::CommitOid::new(&oid_str);
domain.tree_list(&oid)
})
.await
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?;
let response: Vec<TreeEntryResponse> =
entries.into_iter().map(TreeEntryResponse::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_tree_entry(
&self,
namespace: String,
repo_name: String,
query: TreeEntryQuery,
ctx: &Session,
) -> Result<TreeEntryResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let index = query.index;
let entry = tokio::task::spawn_blocking(move || {
let domain = git::GitDomain::from_model(repo)?;
let oid = git::CommitOid::new(&oid_str);
domain.tree_entry(&oid, index)
})
.await
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?;
Ok(TreeEntryResponse::from(entry))
}
pub async fn git_tree_entry_by_path(
&self,
namespace: String,
repo_name: String,
query: TreeEntryByPathQuery,
ctx: &Session,
) -> Result<TreeEntryResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let path = query.path.clone();
let entry = tokio::task::spawn_blocking(move || {
let domain = git::GitDomain::from_model(repo)?;
let oid = git::CommitOid::new(&oid_str);
domain.tree_entry_by_path(&oid, &path)
})
.await
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?;
Ok(TreeEntryResponse::from(entry))
}
pub async fn git_tree_entry_by_commit_path(
&self,
namespace: String,
repo_name: String,
query: TreeEntryByCommitPathQuery,
ctx: &Session,
) -> Result<TreeEntryResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let commit_str = query.commit.clone();
let path = query.path.clone();
let entry = tokio::task::spawn_blocking(move || {
let domain = git::GitDomain::from_model(repo)?;
let commit = git::CommitOid::new(&commit_str);
domain.tree_entry_by_path_from_commit(&commit, &path)
})
.await
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?;
Ok(TreeEntryResponse::from(entry))
}
pub async fn git_tree_entry_count(
&self,
namespace: String,
repo_name: String,
query: TreeGetQuery,
ctx: &Session,
) -> Result<TreeEntryCountResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let count = tokio::task::spawn_blocking(move || {
let domain = git::GitDomain::from_model(repo)?;
let oid = git::CommitOid::new(&oid_str);
domain.tree_entry_count(&oid)
})
.await
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?;
Ok(TreeEntryCountResponse {
oid: query.oid,
count,
})
}
pub async fn git_tree_is_empty(
&self,
namespace: String,
repo_name: String,
query: TreeGetQuery,
ctx: &Session,
) -> Result<TreeIsEmptyResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let is_empty = tokio::task::spawn_blocking(move || {
let domain = git::GitDomain::from_model(repo)?;
let oid = git::CommitOid::new(&oid_str);
domain.tree_is_empty(&oid)
})
.await
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?;
Ok(TreeIsEmptyResponse {
oid: query.oid,
is_empty,
})
}
pub async fn git_tree_diffstats(
&self,
namespace: String,
repo_name: String,
query: TreeDiffQuery,
ctx: &Session,
) -> Result<TreeDiffStatsResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let old_tree_str = query.old_tree.clone();
let new_tree_str = query.new_tree.clone();
let stats = tokio::task::spawn_blocking(move || {
let domain = git::GitDomain::from_model(repo)?;
let old_tree = git::CommitOid::new(&old_tree_str);
let new_tree = git::CommitOid::new(&new_tree_str);
domain.tree_diffstats(&old_tree, &new_tree)
})
.await
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?;
Ok(TreeDiffStatsResponse {
old_tree: query.old_tree,
new_tree: query.new_tree,
files_changed: stats.files_changed,
insertions: stats.insertions,
deletions: stats.deletions,
})
}
}