use std::path::{Path, PathBuf}; use std::process::Command; use crate::{Error, Result}; const MAIN_REF: &str = "refs/heads/main"; pub fn read_head_commit(repo_path: &Path) -> Result { let output = Command::new("git") .arg("-C") .arg(repo_path) .args(["rev-parse", "HEAD"]) .output()?; if !output.status.success() { return Err(Error::Invalid(format!( "git rev-parse HEAD failed with {}", output.status ))); } let head = String::from_utf8_lossy(&output.stdout).trim().to_string(); if head.is_empty() { return Err(Error::Invalid("git rev-parse HEAD returned empty".into())); } Ok(head) } pub fn resolve_git_dir(repo_path: &Path) -> Result { let output = Command::new("git") .arg("-C") .arg(repo_path) .args(["rev-parse", "--git-dir"]) .output()?; if !output.status.success() { return Err(Error::Invalid(format!( "git rev-parse --git-dir failed with {}", output.status ))); } let raw = String::from_utf8_lossy(&output.stdout).trim().to_string(); if raw.is_empty() { return Err(Error::Invalid("git rev-parse --git-dir returned empty".into())); } let git_dir = PathBuf::from(raw); if git_dir.is_absolute() { Ok(git_dir) } else { Ok(repo_path.join(git_dir)) } } pub fn read_main_ref(repo_path: &Path) -> Result { let output = Command::new("git") .arg("-C") .arg(repo_path) .args(["rev-parse", MAIN_REF]) .output()?; if !output.status.success() { return Err(Error::Invalid(format!( "git rev-parse {MAIN_REF} failed with {}", output.status ))); } let head = String::from_utf8_lossy(&output.stdout).trim().to_string(); if head.is_empty() { return Err(Error::Invalid(format!( "git rev-parse {MAIN_REF} returned empty" ))); } Ok(head) } pub fn reset_main_ref(repo_path: &Path, target: &str) -> Result<()> { let status = Command::new("git") .arg("-C") .arg(repo_path) .args(["update-ref", MAIN_REF, target]) .status()?; if !status.success() { return Err(Error::Invalid(format!( "git update-ref {MAIN_REF} failed with {status}" ))); } Ok(()) } pub fn is_fast_forward(repo_path: &Path, old: &str, new: &str) -> Result { let status = Command::new("git") .arg("-C") .arg(repo_path) .args(["merge-base", "--is-ancestor", old, new]) .status()?; if status.success() { return Ok(true); } if status.code() == Some(1) { return Ok(false); } Err(Error::Invalid(format!( "git merge-base --is-ancestor failed with {status}" ))) } #[cfg(test)] mod tests { use super::resolve_git_dir; use std::fs; use std::process::Command; #[test] fn resolve_git_dir_for_working_repo() { let tmp = tempfile::tempdir().unwrap(); let repo = tmp.path().join("repo"); fs::create_dir_all(&repo).unwrap(); let status = Command::new("git") .args([ "-c", "init.defaultBranch=main", "init", repo.to_string_lossy().as_ref(), ]) .status() .unwrap(); assert!(status.success()); let git_dir = resolve_git_dir(&repo).unwrap(); assert!(git_dir.ends_with(".git")); } }