use anyhow::{Context, Result}; use std::process::Command; use std::string::ToString; #[derive(Debug, Clone)] pub struct GitCommit { pub old_path: Option, pub new_path: Option, pub sha: String, pub message: String, pub author: String, } pub fn get_recent_commits(limit: usize) -> Result> { let output = Command::new("git") .args(&[ "log", "--pretty=format:%H|%s|%an", &format!("-n{}", limit), ]) .output() .context("Failed to execute git log")?; if !output.status.success() { return Err(anyhow::anyhow!("Git log command failed")); } let stdout = String::from_utf8_lossy(&output.stdout); let mut commits = Vec::new(); for line in stdout.lines() { let parts: Vec<&str> = line.splitn(3, "|").collect(); if parts.len() >= 3 { commits.push(GitCommit { old_path: None, new_path: None, sha: parts[0].to_string(), message: parts[1].to_string(), author: parts[2].to_string(), }); } } Ok(commits) } pub fn get_diff_from_sha(sha: &str) -> Result> { let output = Command::new("git") .args(&["show", "--stat", sha]) .output() .context("Failed to execute git show")?; if !output.status.success() { return Err(anyhow::anyhow!("Git show command failed")); } let stdout = String::from_utf8_lossy(&output.stdout); let mut changes = Vec::new(); for line in stdout.lines() { if line.contains("|") { let parts: Vec<&str> = line.split("|").collect(); if parts.len() == 2 { let path_parts: Vec<&str> = parts[0].split(" -> ").collect(); let (old_path, new_path) = if path_parts.len() == 2 { (Some(path_parts[0].to_string()), Some(path_parts[1].to_string())) } else { (None, Some(parts[0].to_string())) }; changes.push(Change { old_path, new_path, additions: 0, deletions: 0, }); } } } Ok(changes) } #[derive(Debug)] pub struct Change { pub old_path: Option, pub new_path: Option, pub additions: usize, pub deletions: usize, } pub fn get_staged_diff() -> Result> { let output = Command::new("git") .args(&["diff", "--cached", "--stat"]) .output() .context("Failed to execute git diff --cached")?; if !output.status.success() { return Err(anyhow::anyhow!("Git diff command failed")); } let stdout = String::from_utf8_lossy(&output.stdout); let mut changes = Vec::new(); for line in stdout.lines() { let parts: Vec<&str> = line.split("|").collect(); if parts.len() == 2 { let path_parts: Vec<&str> = parts[0].split(" -> ").collect(); let (old_path, new_path) = if path_parts.len() == 2 { (Some(path_parts[0].to_string()), Some(path_parts[1].to_string())) } else { (None, Some(parts[0].to_string())) }; let stat_parts: Vec<&str> = parts[1].trim().split(" ").collect(); let mut additions = 0; let mut deletions = 0; for stat in stat_parts { if stat.contains('+') && stat.parse::().is_ok() { additions = stat.replace('+', "").parse().unwrap_or(0); } if stat.contains('-') && stat.parse::().is_ok() { deletions = stat.replace('-', "").parse().unwrap_or(0); } } changes.push(Change { old_path, new_path, additions, deletions, }); } } Ok(changes) } pub fn get_diff_stats() -> Result<(usize, usize)> { let output = Command::new("git") .args(&["diff", "--shortstat"]) .output() .context("Failed to execute git diff")?; if !output.status.success() { return Err(anyhow::anyhow!("Git diff command failed")); } let stdout = String::from_utf8_lossy(&output.stdout); let mut insertions = 0; let mut deletions = 0; for line in stdout.lines() { let parts: Vec<&str> = line.split(",").collect(); for part in parts { let part = part.trim(); if let Some(insertions_str) = part.strip_suffix(" insertions(+)") { if let Ok(count) = insertions_str.trim().parse::() { insertions = count; } } if let Some(deletions_str) = part.strip_suffix(" deletions(-)") { if let Ok(count) = deletions_str.trim().parse::() { deletions = count; } } } } Ok((insertions, deletions)) }