use std::path::PathBuf; use crate::errors::GitResult; #[derive(Clone)] pub struct GitBare { pub bare_dir: PathBuf, } #[derive(Debug, Clone)] pub struct LastCommitInfo { pub message: String, pub time: String, pub author_name: String, pub author_email: String, } impl GitBare { pub fn gix_repo(&self) -> crate::errors::GitResult { gix::open(&self.bare_dir).map_err(|e| { crate::errors::GitError::Internal(format!( "failed to open gix repository: {e}" )) }) } pub fn set_default_branch(&self, branch_name: &str) -> GitResult<()> { let output = self.git_command_trusted(vec![ "symbolic-ref".to_string(), "HEAD".to_string(), format!("refs/heads/{}", branch_name), ])?; if !output.success { return Err(crate::errors::GitError::CommandFailed { status_code: output.status_code, stderr: output.stderr_lossy(), }); } Ok(()) } pub fn last_commits_for_paths( &self, paths: &[String], ) -> GitResult>> { use gix::traverse::commit::simple::CommitTimeOrder; if paths.is_empty() { return Ok(vec![]); } let repo = self.gix_repo()?; let head_id = repo.head_id().map_err(|e| { crate::errors::GitError::Internal(format!("no HEAD: {e}")) })?; let walk = repo .rev_walk(vec![head_id.detach()]) .sorting(gix::revision::walk::Sorting::ByCommitTime( CommitTimeOrder::NewestFirst, )) .first_parent_only() .all() .map_err(|e| { crate::errors::GitError::Internal(format!("rev_walk: {e}")) })?; let mut result: Vec> = vec![None; paths.len()]; let mut remaining: std::collections::HashSet = (0..paths.len()).collect(); for walk_item in walk { if remaining.is_empty() { break; } let info = match walk_item { Ok(i) => i, Err(_) => continue, }; let oid = info.id().detach(); let commit = match repo.find_commit(oid) { Ok(c) => c, Err(_) => continue, }; let decoded = match commit.decode() { Ok(d) => d, Err(_) => continue, }; let current_tree = match repo.find_tree(decoded.tree()) { Ok(t) => t, Err(_) => continue, }; let parent_tree = decoded.parents().next().and_then(|pid| { let hex = pid.to_hex().to_string(); let gid = gix::hash::ObjectId::from_hex(hex.as_bytes()).ok()?; let pc = repo.find_commit(gid).ok()?; let p_decoded = pc.decode().ok()?; repo.find_tree(p_decoded.tree()).ok() }); let mut diff_opts = gix::diff::Options::default(); diff_opts.track_path(); let changes = match repo.diff_tree_to_tree( parent_tree.as_ref(), Some(¤t_tree), Some(diff_opts), ) { Ok(d) => d, Err(_) => continue, }; let changed: std::collections::HashSet = changes.iter().map(|c| c.location().to_string()).collect(); let is_root = parent_tree.is_none(); let author_sig = match decoded.author() { Ok(s) => s, Err(_) => continue, }; let time = author_sig.time().unwrap_or(gix::date::Time { seconds: 0, offset: 0, }); let msg = commit .message_raw() .map(|r| r.to_string().trim_end_matches('\n').to_string()) .unwrap_or_default(); let summary = msg.lines().next().unwrap_or("").to_string(); let matched: Vec = remaining .iter() .copied() .filter(|&idx| is_root || changed.contains(&paths[idx])) .collect(); for idx in matched { result[idx] = Some(LastCommitInfo { message: summary.clone(), time: format!("{}", time.seconds), author_name: author_sig.name.to_string(), author_email: author_sig.email.to_string(), }); remaining.remove(&idx); } } Ok(result) } }