352 lines
10 KiB
Rust
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,
|
|
})
|
|
}
|
|
}
|