use crate::{
config::Commit,
git_operations::{commit, get_changes, get_current_branch, get_log, get_repository},
};
use inquire::{Confirm, Select, Text};
use regex::Regex;
use std::process::Command;
pub fn run_commit(commit_config: Commit, fixup: bool) -> Result<(), String> {
if fixup {
run_fixup()?;
return Ok(());
}
let repo = get_repository().map_err(|e| e.to_string())?;
let (_changes, staged) = get_changes(&repo);
if staged.is_empty() {
println!("No staged files found.");
return Ok(());
}
let index = repo
.index()
.map_err(|e| format!("Error accessing index: {}", e))?;
let mut commit_header = if commit_config.conventional {
let type_and_scope = get_type_and_scope(commit_config.types)
.map_err(|e| format!("An error occurred: {}", e))
.map_err(|e| format!("Failed to get confirmation: {}", e))?;
type_and_scope
} else {
String::new()
};
let ticket = if commit_config.ticket {
let re = Regex::new(r"[A-Z]+-[0-9]+").unwrap();
let branch = get_current_branch().unwrap();
re.find(&branch)
.map(|regex_match| format!(" ({})", regex_match.as_str()))
.unwrap_or_default()
} else {
"".to_string()
};
let user_input = Text::new("Enter commit message:")
.prompt()
.map_err(|e| format!("An error occurred: {}", e))?;
let body = if commit_config.conventional {
let mut body_text = Text::new("Body:")
.prompt()
.map_err(|e| format!("An error occurred: {}", e))?;
if !body_text.is_empty() {
body_text = format!("\n\n{}", body_text);
};
body_text
} else {
String::new()
};
let footer = if commit_config.conventional {
let is_breaking_change = Confirm::new("BREAKING CHANGE?")
.with_default(false)
.prompt()
.map_err(|e| format!("Failed to get confirmation: {}", e))?;
let breaking_change = if is_breaking_change {
let breaking_change_desc = Text::new("Breaking change description:")
.prompt()
.map_err(|e| format!("An error occurred: {}", e))?;
commit_header.push('!');
format!("\n\nBREAKING CHANGE: {}", breaking_change_desc)
} else {
String::new()
};
commit_header.push_str(": ");
breaking_change
} else {
String::new()
};
let message = format!(
"{}{}{}{}{}",
commit_header, user_input, ticket, body, footer
);
print_in_box(&message);
let should_commit = Confirm::new("Commit?")
.with_default(true)
.prompt()
.map_err(|e| format!("Failed to get confirmation: {}", e))?;
if should_commit {
commit(repo, index, message).map_err(|e| format!("❌ Commit failed: {}", e))?;
println!("✅ Commit successful!");
} else {
println!("❌ Commit canceled or failed to get user confirmation.");
}
Ok(())
}
fn run_fixup() -> Result<(), String> {
let commits = get_log()?;
let selected_commit = Select::new("Select commit to fixup:", commits)
.prompt()
.map_err(|e| format!("Failed to fixup commit: {}", e))?;
let fixup_output = Command::new("git")
.arg("commit")
.arg("--fixup")
.arg(selected_commit.hash)
.output()
.map_err(|e| format!("Failed to fixup commit: {}", e))?;
if !fixup_output.status.success() {
return Err(format!(
"git commit --fixup failed:\n{}",
String::from_utf8_lossy(&fixup_output.stdout)
));
}
println!("✅ Fixup successful!");
Ok(())
}
fn print_in_box(message: &str) {
let lines: Vec<&str> = message.lines().collect();
let max_len = lines.iter().map(|line| line.len()).max().unwrap_or(0);
println!("┌{}┐", "─".repeat(max_len + 2));
for line in lines {
println!("│ {:width$} │", line, width = max_len);
}
println!("└{}┘", "─".repeat(max_len + 2));
}
fn get_type_and_scope(commit_types: Vec<String>) -> Result<String, String> {
let selected_type = Select::new("Select commit type", commit_types)
.prompt()
.map_err(|e| format!("An error occurred: {}", e))?;
let mut scope = Text::new("Scope:")
.prompt()
.map_err(|e| format!("An error occurred: {}", e))?;
if !scope.is_empty() {
scope = format!("({})", scope);
}
Ok(format!("{}{}", selected_type, scope))
}