use anyhow::Result;
use std::path::Path;
use std::process::Command;
pub struct GitUtils;
impl GitUtils {
pub fn is_git_repo_root(path: &Path) -> bool {
path.join(".git").exists()
}
pub fn find_git_root(start_path: &Path) -> Option<std::path::PathBuf> {
let mut current = start_path;
loop {
if Self::is_git_repo_root(current) {
return Some(current.to_path_buf());
}
match current.parent() {
Some(parent) => current = parent,
None => break,
}
}
None
}
pub fn get_current_commit_hash(repo_path: &Path) -> Result<String> {
let output = Command::new("git")
.arg("rev-parse")
.arg("HEAD")
.current_dir(repo_path)
.output()?;
if !output.status.success() {
return Err(anyhow::anyhow!("Failed to get git commit hash"));
}
Ok(String::from_utf8(output.stdout)?.trim().to_string())
}
pub fn get_changed_files_since_commit(
repo_path: &Path,
since_commit: &str,
) -> Result<Vec<String>> {
let mut changed_files = std::collections::HashSet::new();
let output = Command::new("git")
.args(["diff", "--name-only", since_commit, "HEAD"])
.current_dir(repo_path)
.output()?;
if output.status.success() {
let stdout = String::from_utf8(output.stdout)?;
for line in stdout.lines() {
if !line.trim().is_empty() {
changed_files.insert(line.trim().to_string());
}
}
}
Ok(changed_files.into_iter().collect())
}
pub fn get_staged_files(repo_path: &Path) -> Result<Vec<String>> {
let mut staged_files = Vec::new();
let output = Command::new("git")
.args(["diff", "--cached", "--name-only"])
.current_dir(repo_path)
.output()?;
if output.status.success() {
let stdout = String::from_utf8(output.stdout)?;
for line in stdout.lines() {
if !line.trim().is_empty() {
staged_files.push(line.trim().to_string());
}
}
}
Ok(staged_files)
}
pub fn get_all_changed_files(repo_path: &Path) -> Result<Vec<String>> {
let mut changed_files = std::collections::HashSet::new();
let output = Command::new("git")
.args(["diff", "--cached", "--name-only"])
.current_dir(repo_path)
.output()?;
if output.status.success() {
let stdout = String::from_utf8(output.stdout)?;
for line in stdout.lines() {
if !line.trim().is_empty() {
changed_files.insert(line.trim().to_string());
}
}
}
let output = Command::new("git")
.args(["diff", "--name-only"])
.current_dir(repo_path)
.output()?;
if output.status.success() {
let stdout = String::from_utf8(output.stdout)?;
for line in stdout.lines() {
if !line.trim().is_empty() {
changed_files.insert(line.trim().to_string());
}
}
}
let output = Command::new("git")
.args(["ls-files", "--others", "--exclude-standard"])
.current_dir(repo_path)
.output()?;
if output.status.success() {
let stdout = String::from_utf8(output.stdout)?;
for line in stdout.lines() {
if !line.trim().is_empty() {
changed_files.insert(line.trim().to_string());
}
}
}
Ok(changed_files.into_iter().collect())
}
}