commit_wizard/adapters/git/
cmd.rs1use anyhow::{Context, Result, anyhow};
2use std::io::Write;
3use std::process::Command;
4use tempfile::NamedTempFile;
5
6use crate::ports::git::{CommitOptions, GitPort};
7
8#[derive(Default)]
10pub struct CmdGit;
11
12impl CmdGit {
13 fn run_status(args: &[&str]) -> Result<std::process::ExitStatus> {
14 Command::new("git")
15 .args(args)
16 .status()
17 .with_context(|| format!("failed to launch `git {}`", args.join(" ")))
18 }
19
20 fn run_capture(args: &[&str]) -> Result<std::process::Output> {
21 Command::new("git")
22 .args(args)
23 .output()
24 .with_context(|| format!("failed to launch `git {}`", args.join(" ")))
25 }
26}
27
28impl GitPort for CmdGit {
29 fn commit(&self, message: &str, opts: &CommitOptions) -> Result<()> {
30 if !Self::run_status(&["rev-parse", "--git-dir"])?.success() {
32 return Err(anyhow!(
33 "Not inside a git repository (run `git init` first)."
34 ));
35 }
36
37 if !opts.allow_empty {
39 if Self::run_status(&["diff", "--cached", "--quiet"])?.success() {
41 return Err(anyhow!(
42 "No staged changes. Stage files with `git add -A` (or pass --allow-empty)."
43 ));
44 }
45 }
46
47 let mut tf = NamedTempFile::new().context("failed to create temp file")?;
49 tf.write_all(message.as_bytes())
50 .context("write commit message")?;
51 let msg_path = tf.path().to_string_lossy().to_string();
52
53 let mut args = vec!["commit", "-F", &msg_path];
55 if opts.allow_empty {
56 args.push("--allow-empty");
57 }
58
59 let out = Self::run_capture(&args)?;
60 if !out.status.success() {
61 let stderr = String::from_utf8_lossy(&out.stderr);
62 return Err(anyhow!("git commit failed: {}", stderr.trim()));
63 }
64 let stdout = String::from_utf8_lossy(&out.stdout);
65 if !stdout.trim().is_empty() {
66 println!("{stdout}");
67 }
68 Ok(())
69 }
70}