use std::fs;
use anyhow::Context;
use worktrunk::HookType;
use worktrunk::config::UserConfig;
use worktrunk::git::Repository;
use worktrunk::shell_exec::Cmd;
use worktrunk::styling::println;
use super::super::command_approval::approve_or_skip;
use super::super::commit::{CommitOptions, CommitOutcome, HookGate, StageMode};
use super::super::context::CommandEnv;
use super::super::hooks::HookAnnouncer;
use super::shared::print_dry_run;
pub fn step_commit(
branch: Option<String>,
yes: bool,
verify: bool,
stage: Option<StageMode>,
show_prompt: bool,
dry_run: bool,
) -> anyhow::Result<Option<CommitOutcome>> {
if show_prompt || dry_run {
preview_commit(stage, dry_run)?;
return Ok(None);
}
let mut config = UserConfig::load().context("Failed to load config")?;
let _ = crate::output::prompt_commit_generation(&mut config);
let env = match branch {
Some(ref b) => CommandEnv::for_branch(config, b)?,
None => CommandEnv::for_action(config)?,
};
let ctx = env.context(yes);
let stage_mode = stage.unwrap_or(env.resolved().commit.stage());
let approved = verify
&& approve_or_skip(
&ctx,
&[HookType::PreCommit, HookType::PostCommit],
"Commands declined, committing without hooks",
)?;
let hooks = HookGate::from_approval(verify, approved);
let mut options = CommitOptions::new(&ctx);
options.hooks = hooks;
options.stage_mode = stage_mode;
options.show_no_squash_note = false;
options.warn_about_untracked = stage_mode == StageMode::All;
let mut announcer = HookAnnouncer::new(ctx.repo, ctx.config, false);
let outcome = options.commit(&mut announcer)?;
announcer.flush()?;
Ok(Some(outcome))
}
fn preview_commit(stage: Option<StageMode>, dry_run: bool) -> anyhow::Result<()> {
let env = CommandEnv::for_action(UserConfig::load().context("Failed to load config")?)?;
let commit_config = env.resolved().commit_generation.clone();
let temp_index = if dry_run {
let add_args: Option<&[&str]> = match stage.unwrap_or(env.resolved().commit.stage()) {
StageMode::All => Some(&["add", "-A"]),
StageMode::Tracked => Some(&["add", "-u"]),
StageMode::None => None,
};
add_args
.map(|args| stage_to_temp_index(&env.repo, args))
.transpose()?
} else {
None
};
let index_override = temp_index.as_ref().map(|t| t.path());
let prompt = crate::llm::build_commit_prompt(&commit_config, index_override)?;
if !dry_run {
println!("{}", prompt);
return Ok(());
}
let message = crate::llm::generate_commit_message(&commit_config, index_override)?;
print_dry_run(&prompt, &commit_config, &message)
}
fn stage_to_temp_index(
repo: &Repository,
add_args: &[&str],
) -> anyhow::Result<tempfile::NamedTempFile> {
let wt = repo.current_worktree();
let real_index = wt.git_dir()?.join("index");
let temp = tempfile::NamedTempFile::new().context("Failed to create temporary index")?;
fs::copy(&real_index, temp.path()).context("Failed to copy index file")?;
let output = Cmd::new("git")
.args(add_args.iter().copied())
.current_dir(wt.root()?)
.env("GIT_INDEX_FILE", temp.path())
.run()
.context("Failed to stage changes into temp index")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("git {} failed: {}", add_args.join(" "), stderr.trim());
}
Ok(temp)
}