use crate::AppService; use crate::error::AppError; use crate::git::{ CommitDiffFile, CommitDiffHunk, CommitDiffStats, CommitGraph, CommitMeta, CommitRefInfo, CommitReflogEntry, CommitSignature, CommitSort, CommitWalkOptions, }; use models::repos::repo; use redis::AsyncCommands; use serde::{Deserialize, Serialize}; use session::Session; #[derive(Debug, Clone, Deserialize, utoipa::ToSchema)] pub struct CommitGetQuery { pub oid: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CommitMetaResponse { pub oid: String, pub message: String, pub summary: String, pub author: CommitSignatureResponse, pub committer: CommitSignatureResponse, pub tree_id: String, pub parent_ids: Vec, pub encoding: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CommitSignatureResponse { pub name: String, pub email: String, pub time_secs: i64, pub offset_minutes: i32, } impl From for CommitSignatureResponse { fn from(s: CommitSignature) -> Self { Self { name: s.name, email: s.email, time_secs: s.time_secs, offset_minutes: s.offset_minutes, } } } impl From for CommitMetaResponse { fn from(c: CommitMeta) -> Self { Self { oid: c.oid.to_string(), message: c.message, summary: c.summary, author: CommitSignatureResponse::from(c.author), committer: CommitSignatureResponse::from(c.committer), tree_id: c.tree_id.to_string(), parent_ids: c.parent_ids.into_iter().map(|p| p.to_string()).collect(), encoding: c.encoding, } } } #[derive(Debug, Clone, Serialize)] pub struct CommitExistsResponse { pub oid: String, pub exists: bool, } #[derive(Debug, Clone, Serialize)] pub struct CommitIsCommitResponse { pub oid: String, pub is_commit: bool, } #[derive(Debug, Clone, Serialize)] pub struct CommitMessageResponse { pub oid: String, pub message: String, } #[derive(Debug, Clone, Serialize)] pub struct CommitSummaryResponse { pub oid: String, pub summary: String, } #[derive(Debug, Clone, Serialize)] pub struct CommitShortIdResponse { pub oid: String, pub short_id: String, } #[derive(Debug, Clone, Serialize)] pub struct CommitAuthorResponse { pub oid: String, pub author: CommitSignatureResponse, } #[derive(Debug, Clone, Serialize)] pub struct CommitTreeIdResponse { pub oid: String, pub tree_id: String, } #[derive(Debug, Clone, Serialize)] pub struct CommitParentCountResponse { pub oid: String, pub parent_count: usize, } #[derive(Debug, Clone, Serialize)] pub struct CommitParentIdsResponse { pub oid: String, pub parent_ids: Vec, } #[derive(Debug, Clone, Serialize)] pub struct CommitIsMergeResponse { pub oid: String, pub is_merge: bool, } #[derive(Debug, Clone, Serialize)] pub struct CommitIsTipResponse { pub oid: String, pub is_tip: bool, } #[derive(Debug, Clone, Serialize)] pub struct CommitRefCountResponse { pub oid: String, pub ref_count: usize, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CommitCountResponse { pub count: usize, } #[derive(Debug, Clone, Serialize)] pub struct CommitRefInfoResponse { pub name: String, pub target: String, pub is_remote: bool, pub is_tag: bool, } impl From for CommitRefInfoResponse { fn from(r: CommitRefInfo) -> Self { Self { name: r.name, target: r.target.to_string(), is_remote: r.is_remote, is_tag: r.is_tag, } } } #[derive(Debug, Clone, Serialize)] pub struct CommitBranchesResponse { pub oid: String, pub branches: Vec, } #[derive(Debug, Clone, Serialize)] pub struct CommitTagsResponse { pub oid: String, pub tags: Vec, } #[derive(Debug, Clone, Serialize)] pub struct CommitReflogEntryResponse { pub oid_new: String, pub oid_old: String, pub committer_name: String, pub committer_email: String, pub time_secs: i64, pub message: Option, } impl From for CommitReflogEntryResponse { fn from(e: CommitReflogEntry) -> Self { Self { oid_new: e.oid_new.to_string(), oid_old: e.oid_old.to_string(), committer_name: e.committer_name, committer_email: e.committer_email, time_secs: e.time_secs, message: e.message, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CommitGraphResponse { pub lines: Vec, pub max_parents: usize, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CommitGraphLineResponse { pub oid: String, pub graph_chars: String, pub refs: String, pub short_message: String, } impl From for CommitGraphLineResponse { fn from(l: git::CommitGraphLine) -> Self { Self { oid: l.oid.to_string(), graph_chars: l.graph_chars, refs: l.refs, short_message: l.short_message, } } } impl From for CommitGraphResponse { fn from(g: CommitGraph) -> Self { Self { lines: g .lines .into_iter() .map(CommitGraphLineResponse::from) .collect(), max_parents: g.max_parents, } } } #[derive(Debug, Clone, Serialize)] pub struct CommitDiffStatsResponse { pub oid: String, pub files_changed: usize, pub insertions: usize, pub deletions: usize, } impl From for CommitDiffStatsResponse { fn from(s: CommitDiffStats) -> Self { Self { oid: String::new(), files_changed: s.files_changed, insertions: s.insertions, deletions: s.deletions, } } } #[derive(Debug, Clone, Serialize)] pub struct CommitDiffFileResponse { pub path: Option, pub status: String, pub is_binary: bool, pub size: u64, } impl From for CommitDiffFileResponse { fn from(f: CommitDiffFile) -> Self { Self { path: f.path, status: f.status, is_binary: f.is_binary, size: f.size, } } } #[derive(Debug, Clone, Serialize)] pub struct CommitDiffHunkResponse { pub old_start: u32, pub old_lines: u32, pub new_start: u32, pub new_lines: u32, pub header: String, } impl From for CommitDiffHunkResponse { fn from(h: CommitDiffHunk) -> Self { Self { old_start: h.old_start, old_lines: h.old_lines, new_start: h.new_start, new_lines: h.new_lines, header: h.header, } } } #[derive(Debug, Clone, Deserialize)] pub struct CommitLogQuery { pub rev: Option, pub limit: Option, } #[derive(Debug, Clone, Deserialize)] pub struct CommitWalkQuery { pub rev: Option, pub limit: Option, #[serde(default)] pub first_parent_only: bool, #[serde(default)] pub topological: bool, #[serde(default)] pub reverse: bool, } #[derive(Debug, Clone, Deserialize)] pub struct CommitAncestorsQuery { pub oid: String, pub limit: Option, } #[derive(Debug, Clone, Deserialize)] pub struct CommitDescendantsQuery { pub oid: String, pub limit: Option, } #[derive(Debug, Clone, Deserialize)] pub struct CommitResolveQuery { pub rev: String, } #[derive(Debug, Clone, Deserialize)] pub struct CommitCherryPickRequest { pub cherrypick_oid: String, pub author_name: String, pub author_email: String, pub committer_name: String, pub committer_email: String, pub message: Option, pub mainline: Option, pub update_ref: Option, } #[derive(Debug, Clone, Deserialize)] pub struct CommitCherryPickAbortRequest { pub reset_type: Option, } #[derive(Debug, Clone, Deserialize)] pub struct CommitRevertRequest { pub revert_oid: String, pub author_name: String, pub author_email: String, pub committer_name: String, pub committer_email: String, pub message: Option, pub mainline: Option, pub update_ref: Option, } #[derive(Debug, Clone, Deserialize)] pub struct CommitRevertAbortRequest { pub reset_type: Option, } #[derive(Debug, Clone, Serialize)] pub struct CommitCreateResponse { pub oid: String, } #[derive(Debug, Clone, Deserialize)] pub struct CommitCreateRequest { pub author_name: String, pub author_email: String, pub committer_name: String, pub committer_email: String, pub message: String, pub tree_id: String, pub parent_ids: Vec, pub update_ref: Option, } #[derive(Debug, Clone, Deserialize)] pub struct CommitAmendRequest { pub oid: String, pub author_name: Option, pub author_email: Option, pub committer_name: Option, pub committer_email: Option, pub message: Option, pub message_encoding: Option, pub tree_id: Option, pub update_ref: Option, } #[derive(Debug, Clone, Deserialize)] pub struct CommitDiffQuery { pub oid: String, } macro_rules! git_spawn { ($repo:expr, $domain:ident -> $body:expr) => {{ let repo_clone = $repo.clone(); tokio::task::spawn_blocking(move || { let $domain = git::GitDomain::from_model(repo_clone)?; $body }) .await .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(AppError::from) }}; } impl AppService { pub async fn git_commit_get( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let cache_key = format!( "git:commit:get:{}:{}:{}", 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 repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let meta = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_get(&oid) })?; let response = CommitMetaResponse::from(meta); if let Ok(mut conn) = self.cache.conn().await { let _: Option<()> = conn .set_ex::( cache_key, serde_json::to_string(&response).unwrap_or_default(), 3600, ) .await .ok(); } Ok(response) } pub async fn git_commit_exists( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { 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.commit_exists(&oid)) }) .await .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(AppError::from)?; Ok(CommitExistsResponse { oid: query.oid, exists, }) } pub async fn git_commit_is_commit( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let is_commit = tokio::task::spawn_blocking(move || { let domain = git::GitDomain::from_model(repo)?; let oid = git::CommitOid::new(&oid_str); Ok::<_, git::GitError>(domain.commit_is_commit(&oid)) }) .await .map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))? .map_err(AppError::from)?; Ok(CommitIsCommitResponse { oid: query.oid, is_commit, }) } pub async fn git_commit_message( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let message = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_message(&oid) })?; Ok(CommitMessageResponse { oid: query.oid, message, }) } pub async fn git_commit_summary( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let summary = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_summary(&oid) })?; Ok(CommitSummaryResponse { oid: query.oid, summary, }) } pub async fn git_commit_short_id( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let short_id = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_short_id(&oid) })?; Ok(CommitShortIdResponse { oid: query.oid, short_id, }) } pub async fn git_commit_author( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let author = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_author(&oid) })?; Ok(CommitAuthorResponse { oid: query.oid, author: CommitSignatureResponse::from(author), }) } pub async fn git_commit_tree_id( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let tree_id = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_tree_id(&oid) })?; Ok(CommitTreeIdResponse { oid: query.oid, tree_id: tree_id.to_string(), }) } pub async fn git_commit_parent_count( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let parent_count = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_parent_count(&oid) })?; Ok(CommitParentCountResponse { oid: query.oid, parent_count, }) } pub async fn git_commit_parent_ids( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let parent_ids = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_parent_ids(&oid) })?; Ok(CommitParentIdsResponse { oid: query.oid, parent_ids: parent_ids.into_iter().map(|p| p.to_string()).collect(), }) } pub async fn git_commit_parent( &self, namespace: String, repo_name: String, oid: String, index: usize, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = oid.clone(); let parent = git_spawn!(repo, domain -> { let commit_oid = git::CommitOid::new(&oid_str); domain.commit_parent(&commit_oid, index) })?; Ok(CommitMetaResponse::from(parent)) } pub async fn git_commit_first_parent( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result, AppError> { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let parent = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_first_parent(&oid) })?; Ok(parent.map(CommitMetaResponse::from)) } pub async fn git_commit_is_merge( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let is_merge = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_is_merge(&oid) })?; Ok(CommitIsMergeResponse { oid: query.oid, is_merge, }) } pub async fn git_commit_log( &self, namespace: String, repo_name: String, query: CommitLogQuery, ctx: &Session, ) -> Result, AppError> { let cache_key = format!( "git:commit:log:{}:{}:{:?}:{}", namespace, repo_name, query.rev, query.limit.unwrap_or(0), ); 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 = self.utils_find_repo(namespace, repo_name, ctx).await?; let rev_clone = query.rev.clone(); let limit = query.limit.unwrap_or(0); let commits = git_spawn!(repo, domain -> { domain.commit_log(rev_clone.as_deref(), limit) })?; let response: Vec = commits.into_iter().map(CommitMetaResponse::from).collect(); if let Ok(mut conn) = self.cache.conn().await { let _: Option<()> = conn .set_ex::( cache_key, serde_json::to_string(&response).unwrap_or_default(), 300, ) .await .ok(); } Ok(response) } pub async fn git_commit_count( &self, namespace: String, repo_name: String, from: Option, to: Option, ctx: &Session, ) -> Result { let cache_key = format!( "git:commit:count:{}:{}:{:?}:{:?}", namespace, repo_name, from, to, ); 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 = self.utils_find_repo(namespace, repo_name, ctx).await?; let from_clone = from.clone(); let to_clone = to.clone(); let count = git_spawn!(repo, domain -> { domain.commit_count(from_clone.as_deref(), to_clone.as_deref()) })?; let response = CommitCountResponse { count }; if let Ok(mut conn) = self.cache.conn().await { let _: Option<()> = conn .set_ex::( cache_key, serde_json::to_string(&response).unwrap_or_default(), 300, ) .await .ok(); } Ok(response) } pub async fn git_commit_refs( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result, AppError> { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let refs = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_refs(&oid) })?; Ok(refs.into_iter().map(CommitRefInfoResponse::from).collect()) } pub async fn git_commit_branches( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let branches = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_branches(&oid) })?; Ok(CommitBranchesResponse { oid: query.oid, branches, }) } pub async fn git_commit_tags( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let tags = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_tags(&oid) })?; Ok(CommitTagsResponse { oid: query.oid, tags, }) } pub async fn git_commit_is_tip( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let is_tip = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_is_tip(&oid) })?; Ok(CommitIsTipResponse { oid: query.oid, is_tip, }) } pub async fn git_commit_ref_count( &self, namespace: String, repo_name: String, query: CommitGetQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let ref_count = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_ref_count(&oid) })?; Ok(CommitRefCountResponse { oid: query.oid, ref_count, }) } pub async fn git_commit_reflog( &self, namespace: String, repo_name: String, query: CommitGetQuery, refname: Option, ctx: &Session, ) -> Result, AppError> { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let refname_clone = refname.clone(); let entries = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_reflog(&oid, refname_clone.as_deref()) })?; Ok(entries .into_iter() .map(CommitReflogEntryResponse::from) .collect()) } pub async fn git_commit_graph( &self, namespace: String, repo_name: String, query: CommitWalkQuery, ctx: &Session, ) -> Result { let cache_key = format!( "git:commit:graph:{}:{}:{:?}:{}", namespace, repo_name, query.rev, query.limit.unwrap_or(0), ); 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 = self.utils_find_repo(namespace, repo_name, ctx).await?; let rev_clone = query.rev.clone(); let limit = query.limit.unwrap_or(0); let graph = git_spawn!(repo, domain -> { domain.commit_graph_simple(rev_clone.as_deref(), limit) })?; let response = CommitGraphResponse::from(graph); if let Ok(mut conn) = self.cache.conn().await { let _: Option<()> = conn .set_ex::( cache_key, serde_json::to_string(&response).unwrap_or_default(), 300, ) .await .ok(); } Ok(response) } pub async fn git_commit_walk( &self, namespace: String, repo_name: String, query: CommitWalkQuery, ctx: &Session, ) -> Result, AppError> { let cache_key = format!( "git:commit:walk:{}:{}:{:?}:{}:{}:{}:{}", namespace, repo_name, query.rev, query.limit.unwrap_or(0), query.first_parent_only, query.topological, query.reverse, ); 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 = self.utils_find_repo(namespace, repo_name, ctx).await?; let rev_clone = query.rev.clone(); let limit = query.limit.unwrap_or(0); let first_parent_only = query.first_parent_only; let topological = query.topological; let reverse = query.reverse; let sort = if topological && reverse { CommitSort(CommitSort::TOPOLOGICAL.0 | CommitSort::TIME.0 | CommitSort::REVERSE.0) } else if topological { CommitSort(CommitSort::TOPOLOGICAL.0 | CommitSort::TIME.0) } else if reverse { CommitSort(CommitSort::TIME.0 | CommitSort::REVERSE.0) } else { CommitSort(CommitSort::TOPOLOGICAL.0 | CommitSort::TIME.0) }; let commits = git_spawn!(repo, domain -> { domain.commit_walk(CommitWalkOptions { rev: rev_clone, sort, limit, first_parent_only, }) })?; let response: Vec = commits.into_iter().map(CommitMetaResponse::from).collect(); if let Ok(mut conn) = self.cache.conn().await { let _: Option<()> = conn .set_ex::( cache_key, serde_json::to_string(&response).unwrap_or_default(), 300, ) .await .ok(); } Ok(response) } pub async fn git_commit_ancestors( &self, namespace: String, repo_name: String, query: CommitAncestorsQuery, ctx: &Session, ) -> Result, AppError> { let cache_key = format!( "git:commit:ancestors:{}:{}:{}:{}", namespace, repo_name, query.oid, query.limit.unwrap_or(0), ); 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 = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let limit = query.limit.unwrap_or(0); let commits = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_ancestors(&oid, limit) })?; let response: Vec = commits.into_iter().map(CommitMetaResponse::from).collect(); if let Ok(mut conn) = self.cache.conn().await { let _: Option<()> = conn .set_ex::( cache_key, serde_json::to_string(&response).unwrap_or_default(), 300, ) .await .ok(); } Ok(response) } pub async fn git_commit_descendants( &self, namespace: String, repo_name: String, query: CommitDescendantsQuery, ctx: &Session, ) -> Result, AppError> { let cache_key = format!( "git:commit:descendants:{}:{}:{}:{}", namespace, repo_name, query.oid, query.limit.unwrap_or(0), ); 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 = self.utils_find_repo(namespace, repo_name, ctx).await?; let oid_str = query.oid.clone(); let limit = query.limit.unwrap_or(0); let commits = git_spawn!(repo, domain -> { let oid = git::CommitOid::new(&oid_str); domain.commit_descendants(&oid, limit) })?; let response: Vec = commits.into_iter().map(CommitMetaResponse::from).collect(); if let Ok(mut conn) = self.cache.conn().await { let _: Option<()> = conn .set_ex::( cache_key, serde_json::to_string(&response).unwrap_or_default(), 300, ) .await .ok(); } Ok(response) } pub async fn git_commit_resolve_rev( &self, namespace: String, repo_name: String, query: CommitResolveQuery, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let rev_str = query.rev.clone(); let oid = git_spawn!(repo, domain -> { domain.resolve_rev(&rev_str) })?; Ok(oid.to_string()) } pub async fn git_commit_create( &self, namespace: String, repo_name: String, request: CommitCreateRequest, ctx: &Session, ) -> Result { let repo: repo::Model = self.utils_check_repo_admin(namespace, repo_name, ctx).await?; let parent_ids: Vec<_> = request .parent_ids .iter() .map(|p| git::CommitOid::new(p)) .collect(); let author = git::CommitSignature { name: request.author_name, email: request.author_email, time_secs: chrono::Utc::now().timestamp(), offset_minutes: 0, }; let committer = git::CommitSignature { name: request.committer_name, email: request.committer_email, time_secs: chrono::Utc::now().timestamp(), offset_minutes: 0, }; let tree_id = git::CommitOid::new(&request.tree_id); let update_ref = request.update_ref.clone(); let oid = git_spawn!(repo, domain -> { domain.commit_create( update_ref.as_deref(), &author, &committer, &request.message, &tree_id, &parent_ids, ) })?; Ok(CommitCreateResponse { oid: oid.to_string(), }) } pub async fn git_commit_amend( &self, namespace: String, repo_name: String, request: CommitAmendRequest, ctx: &Session, ) -> Result { let repo: repo::Model = self.utils_check_repo_admin(namespace, repo_name, ctx).await?; let oid = git::CommitOid::new(&request.oid); let author = if request.author_name.is_some() && request.author_email.is_some() { Some(git::CommitSignature { name: request.author_name.unwrap(), email: request.author_email.unwrap(), time_secs: 0, offset_minutes: 0, }) } else { None }; let committer = if request.committer_name.is_some() && request.committer_email.is_some() { Some(git::CommitSignature { name: request.committer_name.unwrap(), email: request.committer_email.unwrap(), time_secs: 0, offset_minutes: 0, }) } else { None }; let tree_id = request.tree_id.as_ref().map(|t| git::CommitOid::new(t)); let update_ref = request.update_ref.clone(); let message_encoding = request.message_encoding.clone(); let message = request.message.clone(); let new_oid = git_spawn!(repo, domain -> { domain.commit_amend( &oid, update_ref.as_deref(), author.as_ref(), committer.as_ref(), message_encoding.as_deref(), message.as_deref(), tree_id.as_ref(), ) })?; Ok(CommitCreateResponse { oid: new_oid.to_string(), }) } pub async fn git_commit_cherry_pick( &self, namespace: String, repo_name: String, request: CommitCherryPickRequest, ctx: &Session, ) -> Result { let repo: repo::Model = self.utils_check_repo_admin(namespace, repo_name, ctx).await?; let cherrypick_oid = git::CommitOid::new(&request.cherrypick_oid); let author = git::CommitSignature { name: request.author_name, email: request.author_email, time_secs: chrono::Utc::now().timestamp(), offset_minutes: 0, }; let committer = git::CommitSignature { name: request.committer_name, email: request.committer_email, time_secs: chrono::Utc::now().timestamp(), offset_minutes: 0, }; let message = request.message.clone(); let mainline = request.mainline.unwrap_or(0); let update_ref = request.update_ref.clone(); let oid = git_spawn!(repo, domain -> { domain.commit_cherry_pick( &cherrypick_oid, &author, &committer, message.as_deref(), mainline, update_ref.as_deref(), ) })?; Ok(CommitCreateResponse { oid: oid.to_string(), }) } pub async fn git_commit_cherry_pick_abort( &self, namespace: String, repo_name: String, request: CommitCherryPickAbortRequest, ctx: &Session, ) -> Result<(), AppError> { let repo: repo::Model = self.utils_check_repo_admin(namespace, repo_name, ctx).await?; let reset_type = request.reset_type.clone(); git_spawn!(repo, domain -> { domain.commit_cherry_pick_abort(reset_type.as_deref().unwrap_or("hard")) })?; Ok(()) } pub async fn git_commit_revert( &self, namespace: String, repo_name: String, request: CommitRevertRequest, ctx: &Session, ) -> Result { let repo: repo::Model = self.utils_check_repo_admin(namespace, repo_name, ctx).await?; let revert_oid = git::CommitOid::new(&request.revert_oid); let author = git::CommitSignature { name: request.author_name, email: request.author_email, time_secs: chrono::Utc::now().timestamp(), offset_minutes: 0, }; let committer = git::CommitSignature { name: request.committer_name, email: request.committer_email, time_secs: chrono::Utc::now().timestamp(), offset_minutes: 0, }; let message = request.message.clone(); let mainline = request.mainline.unwrap_or(0); let update_ref = request.update_ref.clone(); let oid = git_spawn!(repo, domain -> { domain.commit_revert( &revert_oid, &author, &committer, message.as_deref(), mainline, update_ref.as_deref(), ) })?; Ok(CommitCreateResponse { oid: oid.to_string(), }) } pub async fn git_commit_revert_abort( &self, namespace: String, repo_name: String, request: CommitRevertAbortRequest, ctx: &Session, ) -> Result<(), AppError> { let repo: repo::Model = self.utils_check_repo_admin(namespace, repo_name, ctx).await?; let reset_type = request.reset_type.clone(); git_spawn!(repo, domain -> { domain.commit_revert_abort(reset_type.as_deref().unwrap_or("hard")) })?; Ok(()) } }