use gitbaby::commit::types::{CommitInfo, ListCommitsOptions}; use serde::{Deserialize, Serialize}; use crate::service::GitService; use crate::service::cache::{self, IMMUTABLE_TTL}; use crate::share::errors::{GitError, map_baby}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LogListDto { pub commits: Vec, pub cursor: Option, pub has_more: bool, } #[derive(Debug, Clone, Deserialize, utoipa::IntoParams)] pub struct LogOptions { #[serde(default)] pub revision: Option, #[serde(default)] pub limit: Option, #[serde(default)] pub skip: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GraphNode { pub commit: CommitInfo, pub lanes: Vec, } const DEFAULT_LOG_LIMIT: u64 = 50; const MAX_LOG_LIMIT: u64 = 200; impl GitService { pub async fn log_list( &self, space_name: String, repo_name: String, opts: LogOptions, ) -> Result { let limit = opts .limit .unwrap_or(DEFAULT_LOG_LIMIT) .clamp(1, MAX_LOG_LIMIT); let skip = opts.skip.unwrap_or(0); let revision = opts.revision.clone().unwrap_or_else(|| "HEAD".to_string()); let ctx = self.context(space_name, repo_name).await?; let baby = self.baby(&ctx); let rev_oid = baby.rev_parse(&revision).await.map_err(map_baby)?; let key = format!( "repo.{}.log_list.{}:{}:{}", ctx.model.id, rev_oid, skip, limit ); cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async { let max = limit + skip + 1; let max_count = if max <= u32::MAX as u64 { Some(max as u32) } else { None }; let commits = baby .list_commits(ListCommitsOptions { revision: rev_oid.to_string(), max_count, ..Default::default() }) .await .map_err(map_baby)?; let has_more = commits.len() > limit as usize + skip as usize; let mut page = commits; if page.len() > limit as usize + skip as usize { page.truncate(limit as usize + skip as usize); } let page = page.into_iter().skip(skip as usize).collect::>(); let cursor = page.last().map(|c| c.id.to_string()); Ok(LogListDto { commits: page, cursor: if has_more { cursor } else { None }, has_more, }) }) .await } pub async fn log_commits_between( &self, space_name: String, repo_name: String, after: String, before: String, limit: u64, ) -> Result { let limit = limit.clamp(1, MAX_LOG_LIMIT); let ctx = self.context(space_name, repo_name).await?; let baby = self.baby(&ctx); let after_oid = baby.rev_parse(&after).await.map_err(map_baby)?; let before_oid = baby.rev_parse(&before).await.map_err(map_baby)?; let key = format!( "repo.{}.log_between.{}.{}:{}", ctx.model.id, after_oid, before_oid, limit ); cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async { let commits = baby .list_commits(ListCommitsOptions { revision: format!("{after_oid}..{before_oid}"), max_count: Some(limit as u32), ..Default::default() }) .await .map_err(map_baby)?; Ok(LogListDto { commits, cursor: None, has_more: false, }) }) .await } pub async fn log_last_commit( &self, space_name: String, repo_name: String, branch: String, ) -> Result { let ctx = self.context(space_name, repo_name).await?; let baby = self.baby(&ctx); let rev_oid = baby.rev_parse(&branch).await.map_err(map_baby)?; let key = format!("repo.{}.commit.{}", ctx.model.id, rev_oid); cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async { baby.get_commit(&branch).await.map_err(map_baby) }) .await } }