use anyhow::{Context, Result, anyhow};
use std::io::Write;
use std::process::Command;
use tempfile::NamedTempFile;
use crate::ports::git::{CommitOptions, GitPort};
#[derive(Default)]
pub struct CmdGit;
impl CmdGit {
fn run_status(args: &[&str]) -> Result<std::process::ExitStatus> {
Command::new("git")
.args(args)
.status()
.with_context(|| format!("failed to launch `git {}`", args.join(" ")))
}
fn run_capture(args: &[&str]) -> Result<std::process::Output> {
Command::new("git")
.args(args)
.output()
.with_context(|| format!("failed to launch `git {}`", args.join(" ")))
}
}
impl GitPort for CmdGit {
fn commit(&self, message: &str, opts: &CommitOptions) -> Result<()> {
if !Self::run_status(&["rev-parse", "--git-dir"])?.success() {
return Err(anyhow!(
"Not inside a git repository (run `git init` first)."
));
}
if !opts.allow_empty {
if Self::run_status(&["diff", "--cached", "--quiet"])?.success() {
return Err(anyhow!(
"No staged changes. Stage files with `git add -A` (or pass --allow-empty)."
));
}
}
let mut tf = NamedTempFile::new().context("failed to create temp file")?;
tf.write_all(message.as_bytes())
.context("write commit message")?;
let msg_path = tf.path().to_string_lossy().to_string();
let mut args = vec!["commit", "-F", &msg_path];
if opts.allow_empty {
args.push("--allow-empty");
}
let out = Self::run_capture(&args)?;
if !out.status.success() {
let stderr = String::from_utf8_lossy(&out.stderr);
return Err(anyhow!("git commit failed: {}", stderr.trim()));
}
let stdout = String::from_utf8_lossy(&out.stdout);
if !stdout.trim().is_empty() {
println!("{stdout}");
}
Ok(())
}
}