use std::sync::Arc; use cache::AppCache; use tokio_stream::wrappers::ReceiverStream; use tonic::{Request, Response, Status}; use uuid::Uuid; use crate::rpc::{ error::{spawn_blocking_error, to_status}, proto as p, registry::RepoRegistry, }; use crate::sync::ReceiveSyncService; pub struct CommitServiceImpl { pub registry: Arc, pub cache: AppCache, pub sync: ReceiveSyncService, } type CommitHistoryStream = ReceiverStream>; #[tonic::async_trait] impl p::commit_service_server::CommitService for CommitServiceImpl { async fn commit_info( &self, req: Request, ) -> Result, Status> { let inner = req.into_inner(); let bare = self.registry.get(&inner.repo_id).await?; let oid = inner.oid.unwrap_or_default().into(); let result = tokio::task::spawn_blocking(move || bare.commit_info(oid)) .await .map_err(spawn_blocking_error)? .map_err(to_status)?; Ok(Response::new(p::CommitInfoResponse { commit: Some(result.into()), })) } async fn commit_history( &self, req: Request, ) -> Result, Status> { let inner = req.into_inner(); let repo_id = inner.repo_id.clone(); let cache_key = format!( "git:rpc:cache:commit:history:{}:{}:{}:{}:{}", repo_id, inner.limit, inner.skip, inner.sort, inner.branch.as_deref().unwrap_or("") ); if let Ok(Some(cached)) = self.cache.get::(&cache_key).await { return Ok(Response::new(cached)); } let bare = self.registry.get(&repo_id).await?; let params = crate::cmd::commit::CommitWalkParams { start_oids: vec![], hide_oids: vec![], limit: if inner.limit > 0 { Some(inner.limit as usize) } else { None }, skip: inner.skip as usize, first_parent: false, sort: inner.sort.into(), branch: inner.branch.clone(), }; let result = tokio::task::spawn_blocking(move || bare.commit_history(params)) .await .map_err(spawn_blocking_error)? .map_err(to_status)?; let resp = p::CommitHistoryResponse { commits: result.into_iter().map(Into::into).collect(), }; let _ = self.cache.set(&cache_key, &resp).await; Ok(Response::new(resp)) } type CommitHistoryStreamStream = CommitHistoryStream; async fn commit_history_stream( &self, req: Request, ) -> Result, Status> { let inner = req.into_inner(); let bare = self.registry.get(&inner.repo_id).await?; let params = crate::cmd::commit::CommitWalkParams { start_oids: vec![], hide_oids: vec![], limit: if inner.limit > 0 { Some(inner.limit as usize) } else { None }, skip: inner.skip as usize, first_parent: false, sort: inner.sort.into(), branch: inner.branch.clone(), }; let (tx, rx) = tokio::sync::mpsc::channel(128); tokio::task::spawn_blocking(move || { let result = bare.commit_history(params); match result { Ok(commits) => { for c in commits { if tx.blocking_send(Ok(c.into())).is_err() { break; } } } Err(e) => { let _ = tx.blocking_send(Err(to_status(e))); } } }); Ok(Response::new(ReceiverStream::new(rx))) } async fn commit_summary( &self, req: Request, ) -> Result, Status> { let inner = req.into_inner(); let repo_id = inner.repo_id.clone(); let cache_key = format!("git:rpc:cache:commit:summary:{}", repo_id); if let Ok(Some(cached)) = self.cache.get::(&cache_key).await { return Ok(Response::new(cached)); } let bare = self.registry.get(&repo_id).await?; let result = tokio::task::spawn_blocking(move || bare.commit_summary()) .await .map_err(spawn_blocking_error)? .map_err(to_status)?; let resp = p::CommitSummaryResponse { summary: Some(result.into()), }; let _ = self.cache.set(&cache_key, &resp).await; Ok(Response::new(resp)) } async fn commit_walk( &self, req: Request, ) -> Result, Status> { let inner = req.into_inner(); let bare = self.registry.get(&inner.repo_id).await?; let params = inner.params.unwrap_or_default().into(); let result = tokio::task::spawn_blocking(move || bare.commit_walk(params)) .await .map_err(spawn_blocking_error)? .map_err(to_status)?; Ok(Response::new(p::CommitWalkResponse { commits: result.into_iter().map(Into::into).collect(), })) } async fn commit_refs( &self, req: Request, ) -> Result, Status> { let inner = req.into_inner(); let bare = self.registry.get(&inner.repo_id).await?; let result = tokio::task::spawn_blocking(move || { let repo = bare.gix_repo()?; let head_id = repo.head_id()?.detach(); let oid = crate::cmd::oid::ObjectId::new(head_id.to_hex().to_string()); bare.commit_refs(oid) }) .await .map_err(spawn_blocking_error)? .map_err(to_status)?; Ok(Response::new(p::CommitRefsResponse { refs: result.into_iter().map(Into::into).collect(), })) } async fn commit_prefix( &self, req: Request, ) -> Result, Status> { let inner = req.into_inner(); let bare = self.registry.get(&inner.repo_id).await?; let prefix = inner.prefix.clone(); let result = tokio::task::spawn_blocking(move || { bare.commit_oid_from_prefix(&prefix) }) .await .map_err(spawn_blocking_error)? .map_err(to_status)?; Ok(Response::new(p::CommitPrefixResponse { oid: Some(result.into()), })) } async fn commit_exists( &self, req: Request, ) -> Result, Status> { let inner = req.into_inner(); let bare = self.registry.get(&inner.repo_id).await?; let oid: crate::cmd::oid::ObjectId = inner.oid.unwrap_or_default().into(); let result = tokio::task::spawn_blocking(move || bare.commit_exists(oid)) .await .map_err(spawn_blocking_error)? .map_err(to_status)?; Ok(Response::new(p::CommitExistsResponse { exists: result })) } async fn cherry_pick( &self, req: Request, ) -> Result, Status> { let inner = req.into_inner(); let repo_id = inner.repo_id.clone(); let bare = self.registry.get(&inner.repo_id).await?; let params = inner.params.unwrap_or_default().into(); let result = tokio::task::spawn_blocking(move || bare.commit_pick(params)) .await .map_err(spawn_blocking_error)? .map_err(to_status)?; if let Ok(repo_uid) = Uuid::parse_str(&repo_id) { self.sync .send(crate::sync::RepoReceiveSyncTask { repo_uid }) .await; } Ok(Response::new(p::CherryPickResponse { oid: Some(result.into()), })) } async fn cherry_pick_sequence( &self, req: Request, ) -> Result, Status> { let inner = req.into_inner(); let repo_id = inner.repo_id.clone(); let bare = self.registry.get(&inner.repo_id).await?; let params = inner.params.unwrap_or_default().into(); let result = tokio::task::spawn_blocking(move || { bare.commit_cherry_pick_sequence(params) }) .await .map_err(spawn_blocking_error)? .map_err(to_status)?; if let Ok(repo_uid) = Uuid::parse_str(&repo_id) { self.sync .send(crate::sync::RepoReceiveSyncTask { repo_uid }) .await; } Ok(Response::new(p::CherryPickSequenceResponse { oid: Some(result.into()), })) } async fn create_commit( &self, req: Request, ) -> Result, Status> { let inner = req.into_inner(); let repo_id = inner.repo_id.clone(); let bare = self.registry.get(&inner.repo_id).await?; let params = crate::cmd::commit::CreateCommitParams { branch: inner.branch.clone(), message: inner.message.clone(), author_name: inner.author_name.clone(), author_email: inner.author_email.clone(), committer_name: inner.committer_name.clone(), committer_email: inner.committer_email.clone(), files: inner .files .into_iter() .map(|f| crate::cmd::commit::FileChange { path: f.path, content: f.content, }) .collect(), }; let result = tokio::task::spawn_blocking(move || bare.commit_create(params)) .await .map_err(spawn_blocking_error)? .map_err(to_status)?; // Trigger sync after write if let Ok(repo_uid) = Uuid::parse_str(&repo_id) { self.sync .send(crate::sync::RepoReceiveSyncTask { repo_uid }) .await; } Ok(Response::new(p::CreateCommitResponse { oid: Some(result.into()), })) } }