gitdataai/lib/git/graphql/commit.rs
2026-05-30 01:38:40 +08:00

225 lines
5.8 KiB
Rust

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<String>,
pub encoding: Option<String>,
}
#[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<CommitSignature> 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<CommitMeta> 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<CommitGql> {
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<i32>,
skip: Option<i32>,
sort: Option<String>,
) -> anyhow::Result<Vec<CommitGql>> {
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<CommitSummaryGql> {
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<CommitGql>,
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<CommitSummary> for CommitSummaryGql {
fn from(s: CommitSummary) -> Self {
CommitSummaryGql {
head: s.head.map(CommitGql::from),
count: s.count as i32,
}
}
}