use crate::rpcgit::commit::types::{CommitInfo, ListCommitsOptions}; use serde::{Deserialize, Serialize}; use crate::context::facade::GitContext; 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, ctx: &GitContext, 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 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 } }