use git::rpc::{proto as p, proto::commit_service_client::CommitServiceClient}; use session::Session; use crate::{AppService, error::AppError, git::rpc_err}; #[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)] pub struct CompareResponse { pub base_commit: CompareCommit, pub head_commit: CompareCommit, pub ahead_by: i32, pub behind_by: i32, pub total_commits: i32, pub commits: Vec, pub files_changed: u64, pub insertions: u64, pub deletions: u64, } #[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)] pub struct CompareCommit { pub sha: String, pub message: String, pub author_name: Option, pub author_email: Option, } impl AppService { pub async fn git_compare( &self, ctx: &Session, wk_name: &str, repo_name: &str, base: &str, head: &str, ) -> Result { let repo = self.git_require_member(ctx, wk_name, repo_name).await?; let mut client = CommitServiceClient::new(self.git.clone()); fn oid(s: &str) -> p::ObjectId { p::ObjectId { value: s.to_string(), } } let base_info = client .commit_info(tonic::Request::new(p::CommitInfoRequest { repo_id: repo.id.to_string(), oid: Some(oid(base)), })) .await .map_err(rpc_err)? .into_inner(); let head_info = client .commit_info(tonic::Request::new(p::CommitInfoRequest { repo_id: repo.id.to_string(), oid: Some(oid(head)), })) .await .map_err(rpc_err)? .into_inner(); let history = client .commit_history(tonic::Request::new(p::CommitHistoryRequest { repo_id: repo.id.to_string(), limit: 250, skip: 0, sort: 0, branch: Some(format!("{base}..{head}")), })) .await .map_err(rpc_err)? .into_inner(); let commits: Vec = history .commits .into_iter() .map(|c| { let author_name = c.author.as_ref().map(|a| a.name.clone()); let author_email = c.author.as_ref().map(|a| a.email.clone()); CompareCommit { sha: c.oid.map(|o| o.value).unwrap_or_default(), message: c.summary, author_name, author_email, } }) .collect(); let diff = crate::AppService::git_diff_stats( self, ctx, wk_name, repo_name, base.to_string(), head.to_string(), None, ) .await?; let stats = diff.result.and_then(|r| r.stats); let files_changed = stats.as_ref().map(|s| s.files_changed).unwrap_or(0); let insertions = stats.as_ref().map(|s| s.insertions).unwrap_or(0); let deletions = stats.as_ref().map(|s| s.deletions).unwrap_or(0); Ok(CompareResponse { base_commit: cmt(base_info.commit), head_commit: cmt(head_info.commit), ahead_by: commits.len() as i32, behind_by: 0, total_commits: commits.len() as i32, commits, files_changed, insertions, deletions, }) } } fn cmt(c: Option) -> CompareCommit { c.map(|c| { let author_name = c.author.as_ref().map(|a| a.name.clone()); let author_email = c.author.as_ref().map(|a| a.email.clone()); CompareCommit { sha: c.oid.map(|o| o.value).unwrap_or_default(), message: c.message, author_name, author_email, } }) .unwrap_or_else(|| CompareCommit { sha: String::new(), message: String::new(), author_name: None, author_email: None, }) }