chore(service/git): minor fixes in service layer git operations
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions

Small adjustments to commit, init, refs, star, and watch operations
in the service layer.
This commit is contained in:
ZhenYi 2026-04-27 08:28:27 +08:00
parent 64dc27161b
commit 3f1f0d5e23
5 changed files with 61 additions and 28 deletions

View File

@ -803,9 +803,20 @@ impl AppService {
commits.into_iter().map(CommitMetaResponse::from).collect(); commits.into_iter().map(CommitMetaResponse::from).collect();
// Get total count for pagination metadata. // Get total count for pagination metadata.
// Must use the same cache key format as git_commit_count:
// git:commit:count:{namespace}:{repo_name}:{from:?}:{to:?}
// where from/to correspond to the rev spec passed to commit_log.
let (from, to) = match rev_for_count {
Some(rev) if rev.contains("..") => {
let parts: Vec<&str> = rev.splitn(2, "..").collect();
(Some(parts[0].to_string()), Some(parts[1].to_string()))
}
Some(rev) => (None, Some(rev)),
None => (None, None),
};
let total_cache_key = format!( let total_cache_key = format!(
"git:commit:count:{}:{}:{:?}", "git:commit:count:{}:{}:{:?}:{:?}",
namespace, repo_name, rev_for_count, namespace, repo_name, from, to,
); );
let total: usize = if let Ok(mut conn) = self.cache.conn().await { let total: usize = if let Ok(mut conn) = self.cache.conn().await {
if let Ok(cached) = conn.get::<_, String>(total_cache_key.clone()).await { if let Ok(cached) = conn.get::<_, String>(total_cache_key.clone()).await {
@ -1326,7 +1337,7 @@ impl AppService {
} else if reverse { } else if reverse {
CommitSort(CommitSort::TIME.0 | CommitSort::REVERSE.0) CommitSort(CommitSort::TIME.0 | CommitSort::REVERSE.0)
} else { } else {
CommitSort(CommitSort::TOPOLOGICAL.0 | CommitSort::TIME.0) CommitSort(CommitSort::TIME.0)
}; };
let commits = git_spawn!(repo, domain -> { let commits = git_spawn!(repo, domain -> {

View File

@ -39,7 +39,7 @@ impl AppService {
let domain = git::GitDomain::open_workdir(&path).map_err(AppError::from)?; let domain = git::GitDomain::open_workdir(&path).map_err(AppError::from)?;
Ok(GitInitResponse { Ok(GitInitResponse {
path: domain.repo().path().to_string_lossy().to_string(), path: domain.repo().path().to_string_lossy().to_string(),
is_bare: true, is_bare: false,
}) })
} }

View File

@ -6,6 +6,45 @@ use redis::AsyncCommands;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use session::Session; use session::Session;
/// Delete all cached ref list entries for a given namespace/repo.
/// Redis DEL does not support glob patterns, so we SCAN and delete each key.
async fn invalidate_ref_cache(
cache: &db::cache::AppCache,
namespace: &str,
repo_name: &str,
) {
let prefix = format!("git:ref:list:{}:{}:", namespace, repo_name);
if let Ok(mut conn) = cache.conn().await {
let pattern = format!("{}*", prefix);
let mut cursor: u64 = 0;
loop {
match redis::cmd("SCAN")
.arg(cursor)
.arg("MATCH")
.arg(&pattern)
.arg("COUNT")
.arg(100)
.query_async::<(u64, Vec<String>)>(&mut conn)
.await
{
Ok((new_cursor, keys)) => {
for key in &keys {
let _: () = conn.del(key).await.unwrap_or(());
}
if new_cursor == 0 {
break;
}
cursor = new_cursor;
}
Err(e) => {
tracing::debug!(error = ?e, "cache scan failed (non-fatal)");
break;
}
}
}
}
}
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)] #[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
pub struct RefListQuery { pub struct RefListQuery {
pub pattern: Option<String>, pub pattern: Option<String>,
@ -201,12 +240,7 @@ impl AppService {
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?; .map_err(AppError::from)?;
if let Ok(mut conn) = self.cache.conn().await { invalidate_ref_cache(&self.cache, &namespace, &repo_name).await;
let key = format!("git:ref:list:{}:{}:*", namespace, repo_name);
if let Err(e) = conn.del::<String, ()>(key).await {
tracing::debug!(error = ?e, "cache del failed (non-fatal)");
}
}
Ok(RefUpdateResponse { Ok(RefUpdateResponse {
name: result.name, name: result.name,
@ -234,12 +268,7 @@ impl AppService {
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?; .map_err(AppError::from)?;
if let Ok(mut conn) = self.cache.conn().await { invalidate_ref_cache(&self.cache, &namespace, &repo_name).await;
let key = format!("git:ref:list:{}:{}:*", namespace, repo_name);
if let Err(e) = conn.del::<String, ()>(key).await {
tracing::debug!(error = ?e, "cache del failed (non-fatal)");
}
}
Ok(RefDeleteResponse { Ok(RefDeleteResponse {
name, name,
@ -269,12 +298,7 @@ impl AppService {
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?; .map_err(AppError::from)?;
if let Ok(mut conn) = self.cache.conn().await { invalidate_ref_cache(&self.cache, &namespace, &repo_name).await;
let key = format!("git:ref:list:{}:{}:*", namespace, repo_name);
if let Err(e) = conn.del::<String, ()>(key).await {
tracing::debug!(error = ?e, "cache del failed (non-fatal)");
}
}
Ok(RefInfoResponse::from(info)) Ok(RefInfoResponse::from(info))
} }

View File

@ -39,7 +39,7 @@ impl AppService {
.one(&self.db) .one(&self.db)
.await?; .await?;
if existing.is_some() { if existing.is_some() {
return Err(AppError::InternalServerError("already starred".to_string())); return Err(AppError::Conflict("already starred".to_string()));
} }
RepoStar::insert(repo_star::ActiveModel { RepoStar::insert(repo_star::ActiveModel {
id: Default::default(), id: Default::default(),
@ -97,7 +97,7 @@ impl AppService {
.exec(&self.db) .exec(&self.db)
.await?; .await?;
if deleted.rows_affected == 0 { if deleted.rows_affected == 0 {
return Err(AppError::InternalServerError("not starred".to_string())); return Err(AppError::NotFound("not starred".to_string()));
} }
let project_id = match repo_model::Entity::find_by_id(repo.id).one(&self.db).await { let project_id = match repo_model::Entity::find_by_id(repo.id).one(&self.db).await {
Ok(Some(r)) => r.project, Ok(Some(r)) => r.project,

View File

@ -51,9 +51,7 @@ impl AppService {
.one(&self.db) .one(&self.db)
.await?; .await?;
if existing.is_some() { if existing.is_some() {
return Err(AppError::InternalServerError( return Err(AppError::Conflict("already watching".to_string()));
"already watching".to_string(),
));
} }
RepoWatch::insert(repo_watch::ActiveModel { RepoWatch::insert(repo_watch::ActiveModel {
id: Default::default(), id: Default::default(),
@ -110,7 +108,7 @@ impl AppService {
.exec(&self.db) .exec(&self.db)
.await?; .await?;
if deleted.rows_affected == 0 { if deleted.rows_affected == 0 {
return Err(AppError::InternalServerError("not watching".to_string())); return Err(AppError::NotFound("not watching".to_string()));
} }
let project_id = match repo_model::Entity::find_by_id(repo.id).one(&self.db).await { let project_id = match repo_model::Entity::find_by_id(repo.id).one(&self.db).await {
Ok(Some(r)) => r.project, Ok(Some(r)) => r.project,