use std::time::Duration; use juniper::graphql_object; use serde::{Deserialize, Serialize}; use crate::{ cmd::{ commit::{ CommitMeta, CommitSignature, commit_summary::CommitSummary, commit_walker::{CommitWalkParams, CommitWalkSort}, }, oid::ObjectId, }, graphql::{ GraphqlContext, cache_helper::{ IMMUTABLE_TTL, MUTABLE_TTL, cache_key, cached_json, mutable_cache_key, repo_revision, }, }, }; const COMMIT_TTL: Duration = IMMUTABLE_TTL; #[derive(Clone, Serialize, Deserialize)] pub struct CommitGql { pub oid: String, pub message: String, pub summary: String, pub author: CommitSignatureGql, pub committer: CommitSignatureGql, pub tree_id: String, pub parent_ids: Vec, pub encoding: Option, } #[derive(Clone, Serialize, Deserialize)] pub struct CommitSignatureGql { pub name: String, pub email: String, pub time_secs: f64, pub offset_minutes: i32, } #[graphql_object(context = GraphqlContext)] impl CommitSignatureGql { fn name(&self) -> &str { &self.name } fn email(&self) -> &str { &self.email } fn time_secs(&self) -> f64 { self.time_secs } fn offset_minutes(&self) -> i32 { self.offset_minutes } } #[graphql_object(context = GraphqlContext)] impl CommitGql { fn oid(&self) -> &str { &self.oid } fn message(&self) -> &str { &self.message } fn summary(&self) -> &str { &self.summary } fn author(&self) -> &CommitSignatureGql { &self.author } fn committer(&self) -> &CommitSignatureGql { &self.committer } fn tree_id(&self) -> &str { &self.tree_id } fn parent_ids(&self) -> &[String] { &self.parent_ids } fn encoding(&self) -> Option<&str> { self.encoding.as_deref() } } impl From for CommitSignatureGql { fn from(s: CommitSignature) -> Self { CommitSignatureGql { name: s.name, email: s.email, time_secs: s.time_secs as f64, offset_minutes: s.offset_minutes, } } } impl From for CommitGql { fn from(m: CommitMeta) -> Self { CommitGql { oid: m.oid.0, message: m.message, summary: m.summary, author: CommitSignatureGql::from(m.author), committer: CommitSignatureGql::from(m.committer), tree_id: m.tree_id.0, parent_ids: m.parent_ids.iter().map(|p| p.0.clone()).collect(), encoding: m.encoding, } } } pub async fn resolve_commit( ctx: &GraphqlContext, oid: String, ) -> anyhow::Result { let key = cache_key("query:git:commit", &[&oid]); cached_json(&ctx.cache, &key, COMMIT_TTL, || { let repo = ctx.repo.clone(); let oid_obj = ObjectId::new(&oid); async move { let meta = tokio::task::spawn_blocking(move || repo.commit_info(oid_obj)) .await? .map_err(|e| anyhow::anyhow!(e))?; Ok(CommitGql::from(meta)) } }) .await } pub async fn resolve_commit_history( ctx: &GraphqlContext, limit: Option, skip: Option, sort: Option, ) -> anyhow::Result> { let revision = repo_revision(ctx).await; let sort_str = sort.as_deref().unwrap_or("time"); let key_parts = format!( "{}:{}:{}:{}", limit.map(|l| l.to_string()).unwrap_or_default(), skip.map(|s| s.to_string()).unwrap_or_default(), sort_str, revision, ); let key = mutable_cache_key( ctx, "query:git:commit_history", &[&key_parts], &revision, ); cached_json(&ctx.cache, &key, MUTABLE_TTL, || { let repo = ctx.repo.clone(); let walk_sort = match sort_str { "topological" => CommitWalkSort::Topological, "time" => CommitWalkSort::Time, "reverse" => CommitWalkSort::Reverse, _ => CommitWalkSort::Time, }; let params = CommitWalkParams { limit: limit.map(|l| l as usize), skip: skip.unwrap_or(0) as usize, sort: walk_sort, ..Default::default() }; async move { let commits = tokio::task::spawn_blocking(move || { repo.commit_history(params) }) .await? .map_err(|e| anyhow::anyhow!(e))?; Ok(commits.into_iter().map(CommitGql::from).collect()) } }) .await } pub async fn resolve_commit_summary( ctx: &GraphqlContext, ) -> anyhow::Result { let revision = repo_revision(ctx).await; let key = mutable_cache_key(ctx, "query:git:commit_summary", &[], &revision); cached_json(&ctx.cache, &key, MUTABLE_TTL, || { let repo = ctx.repo.clone(); async move { let summary = tokio::task::spawn_blocking(move || repo.commit_summary()) .await? .map_err(|e| anyhow::anyhow!(e))?; Ok(CommitSummaryGql::from(summary)) } }) .await } #[derive(Clone, Serialize, Deserialize)] pub struct CommitSummaryGql { pub head: Option, pub count: i32, } #[graphql_object(context = GraphqlContext)] impl CommitSummaryGql { fn head(&self) -> Option<&CommitGql> { self.head.as_ref() } fn count(&self) -> i32 { self.count } } impl From for CommitSummaryGql { fn from(s: CommitSummary) -> Self { CommitSummaryGql { head: s.head.map(CommitGql::from), count: s.count as i32, } } }