use anyhow::{anyhow, Result};
use tempfile::TempDir;
use std::path::Path;
use std::process::Command;
use crate::config::get_commit_config;
use super::file_pattern::filter_excluded_files;
pub fn get_staged_files(repo_path: &Path) -> Result<Vec<String>> {
let output = Command::new("git")
.current_dir(repo_path)
.args(["diff", "--staged", "--name-only"])
.output()?;
if !output.status.success() {
let error = String::from_utf8_lossy(&output.stderr);
return Err(anyhow!("Failed to get staged files: {}", error));
}
let files_output = String::from_utf8(output.stdout)?;
Ok(files_output.lines().map(|s| s.to_string()).collect())
}
pub fn get_staged_diff(repo_path: &Path) -> Result<String> {
let files = get_staged_files(repo_path)?;
let diff_files = filter_excluded_files(files, get_commit_config()?.exclude);
let output = Command::new("git")
.current_dir(repo_path)
.args(["diff", "--staged", "--"])
.args(diff_files)
.output()?;
if !output.status.success() {
let error = String::from_utf8_lossy(&output.stderr);
return Err(anyhow!("Failed to get staged diff: {}", error));
}
let diff_output = String::from_utf8(output.stdout)?;
Ok(diff_output)
}
pub fn create_commit(repo_path: &Path, message: &str) -> Result<()> {
let temp_dir = TempDir::with_prefix("create_commit")?;
let message_file = temp_dir.path().join("commit_message.txt");
std::fs::write(&message_file, message)?;
let output = Command::new("git")
.current_dir(repo_path)
.args(["commit", "-F", message_file.to_str().unwrap()])
.output()?;
let _ = std::fs::remove_file(message_file);
if !output.status.success() {
let error = String::from_utf8_lossy(&output.stderr);
return Err(anyhow!("Failed to create commit: {}", error));
}
Ok(())
}
pub fn get_last_commit_files(repo_path: &Path) -> Result<Vec<String>> {
let output = Command::new("git")
.current_dir(repo_path)
.args(["show", "--format=", "--name-only"])
.output()?;
if !output.status.success() {
let error = String::from_utf8_lossy(&output.stderr);
return Err(anyhow!("Failed to get last commit files: {}", error));
}
let files_output = String::from_utf8(output.stdout)?;
let files: Vec<String> = files_output.lines().map(|s| s.to_string()).collect();
Ok(files)
}
pub fn get_last_commit_diff(repo_path: &Path) -> Result<String> {
let files = get_last_commit_files(repo_path)?;
let diff_files = filter_excluded_files(files, get_commit_config()?.exclude);
if diff_files.is_empty() {
return Err(anyhow!("No last commit files to diff"));
}
let output = Command::new("git")
.current_dir(repo_path)
.args(["show", "--format=%", "HEAD", "--"])
.args(diff_files)
.output()?;
if !output.status.success() {
let error = String::from_utf8_lossy(&output.stderr);
return Err(anyhow!("Failed to get last commit diff: {}", error));
}
let diff_output = String::from_utf8(output.stdout)?;
Ok(diff_output)
}
pub fn amend_commit(repo_path: &Path, message: &str) -> Result<()> {
let temp_dir = TempDir::with_prefix("amend_commit")?;
let message_file = temp_dir.path().join("commit_message.txt");
std::fs::write(&message_file, message)?;
let output = Command::new("git")
.current_dir(repo_path)
.args([
"commit",
"--amend",
"--file",
message_file.to_str().unwrap(),
])
.output()?;
let _ = std::fs::remove_file(message_file);
if !output.status.success() {
let error = String::from_utf8_lossy(&output.stderr);
return Err(anyhow!("Failed to amend commit: {}", error));
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::fs;
use tempfile::TempDir;
use super::*;
#[test]
fn test_create_commit() -> Result<()> {
let temp_dir = TempDir::with_prefix("test_create_commit")?;
let repo_path = temp_dir.path();
Command::new("git")
.current_dir(repo_path)
.args(["init"])
.output()?;
let test_file_path = repo_path.join("test.txt");
fs::write(&test_file_path, "Test content")?;
Command::new("git")
.current_dir(repo_path)
.args(["add", "test.txt"])
.output()?;
Command::new("git")
.current_dir(repo_path)
.args(["config", "user.name", "Test User"])
.output()?;
Command::new("git")
.current_dir(repo_path)
.args(["config", "user.email", "test@example.com"])
.output()?;
let message = "Test commit message";
create_commit(repo_path, message)?;
let output = Command::new("git")
.current_dir(repo_path)
.args(["log", "-1", "--pretty=format:%s|%an|%ae"])
.output()?;
let commit_info = String::from_utf8(output.stdout)?;
let parts: Vec<&str> = commit_info.split('|').collect();
assert_eq!(parts[0], message);
assert_eq!(parts[1], "Test User");
assert_eq!(parts[2], "test@example.com");
Ok(())
}
#[test]
fn test_get_last_commit_diff() -> Result<()> {
let temp_dir = TempDir::with_prefix("test_get_last_commit_diff")?;
let repo_path = temp_dir.path();
Command::new("git")
.current_dir(repo_path)
.args(["init"])
.output()?;
Command::new("git")
.current_dir(repo_path)
.args(["config", "user.name", "Test User"])
.output()?;
Command::new("git")
.current_dir(repo_path)
.args(["config", "user.email", "test@example.com"])
.output()?;
let test_file_path = repo_path.join("test.txt");
fs::write(&test_file_path, "Test content")?;
Command::new("git")
.current_dir(repo_path)
.args(["add", "test.txt"])
.output()?;
Command::new("git")
.current_dir(repo_path)
.args(["commit", "-m", "Initial commit"])
.output()?;
let diff = get_last_commit_diff(repo_path)?;
assert!(diff.contains("+Test content"));
assert!(diff.contains("test.txt"));
Ok(())
}
#[test]
fn test_amend_commit() -> Result<()> {
let temp_dir = TempDir::new()?;
let repo_path = temp_dir.path();
Command::new("git")
.current_dir(repo_path)
.args(["init"])
.output()?;
Command::new("git")
.current_dir(repo_path)
.args(["config", "user.name", "Test User"])
.output()?;
Command::new("git")
.current_dir(repo_path)
.args(["config", "user.email", "test@example.com"])
.output()?;
let test_file_path = repo_path.join("test.txt");
fs::write(&test_file_path, "Test content")?;
Command::new("git")
.current_dir(repo_path)
.args(["add", "test.txt"])
.output()?;
create_commit(repo_path, "Initial commit")?;
let output = Command::new("git")
.current_dir(repo_path)
.args(["log", "-1", "--pretty=format:%s"])
.output()?;
let original_message = String::from_utf8(output.stdout)?;
assert_eq!(original_message, "Initial commit");
let new_message = "Amended commit message";
amend_commit(repo_path, new_message)?;
let output = Command::new("git")
.current_dir(repo_path)
.args(["log", "-1", "--pretty=format:%s"])
.output()?;
let amended_message = String::from_utf8(output.stdout)?;
assert_eq!(amended_message.trim(), new_message);
Ok(())
}
#[test]
fn test_get_staged_diff() -> Result<()> {
let temp_dir = TempDir::new()?;
let repo_path = temp_dir.path();
Command::new("git")
.current_dir(repo_path)
.args(["init"])
.output()?;
Command::new("git")
.current_dir(repo_path)
.args(["config", "user.name", "Test User"])
.output()?;
Command::new("git")
.current_dir(repo_path)
.args(["config", "user.email", "test@example.com"])
.output()?;
let test_file_path = repo_path.join("test.txt");
fs::write(&test_file_path, "Initial content")?;
Command::new("git")
.current_dir(repo_path)
.args(["add", "test.txt"])
.output()?;
Command::new("git")
.current_dir(repo_path)
.args(["commit", "-m", "Initial commit"])
.output()?;
fs::write(&test_file_path, "Modified content")?;
Command::new("git")
.current_dir(repo_path)
.args(["add", "test.txt"])
.output()?;
let diff = get_staged_diff(repo_path)?;
assert!(diff.contains("+Modified content"));
assert!(diff.contains("-Initial content"));
Ok(())
}
}