use crate::engine::{BranchMetadata, Stack};
use crate::git::GitRepo;
use anyhow::{Context, Result};
use colored::Colorize;
use dialoguer::{theme::ColorfulTheme, Confirm};
use std::process::Command;
pub fn run(keep_branch: bool, skip_confirm: bool) -> Result<()> {
let repo = GitRepo::open()?;
let current = repo.current_branch()?;
let stack = Stack::load(&repo)?;
let workdir = repo.workdir()?;
let meta = BranchMetadata::read(repo.inner(), ¤t)?
.context("Current branch is not tracked. Use 'stax branch track' first.")?;
let parent = &meta.parent_branch_name;
if parent == &stack.trunk {
println!(
"{}",
"Cannot fold into trunk. Use 'stax submit' to merge into trunk via PR.".yellow()
);
return Ok(());
}
if let Some(branch_info) = stack.branches.get(¤t) {
if !branch_info.children.is_empty() {
println!("{}", "Cannot fold: this branch has children.".red());
println!("Children: {}", branch_info.children.join(", ").cyan());
println!();
println!("Please fold or delete child branches first.");
return Ok(());
}
}
let output = Command::new("git")
.args(["rev-list", "--count", &format!("{}..HEAD", parent)])
.current_dir(workdir)
.output()
.context("Failed to count commits")?;
let commit_count: usize = String::from_utf8_lossy(&output.stdout)
.trim()
.parse()
.unwrap_or(0);
if commit_count == 0 {
println!("{}", "No commits to fold.".yellow());
return Ok(());
}
println!(
"Folding '{}' ({} commit(s)) into '{}'",
current.cyan(),
commit_count.to_string().cyan(),
parent.green()
);
let log_output = Command::new("git")
.args(["log", "--oneline", &format!("{}..HEAD", parent)])
.current_dir(workdir)
.output()
.context("Failed to show commits")?;
println!();
println!("{}", "Commits to fold:".bold());
for line in String::from_utf8_lossy(&log_output.stdout).lines() {
println!(" {}", line.dimmed());
}
println!();
let action = if keep_branch {
"fold"
} else {
"fold and delete"
};
if !skip_confirm {
let confirm = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(format!("{} '{}' into '{}'?", action, current, parent))
.default(true)
.interact()?;
if !confirm {
println!("{}", "Aborted.".red());
return Ok(());
}
}
print!("Checking out {}... ", parent.cyan());
let checkout_status = Command::new("git")
.args(["checkout", parent])
.current_dir(workdir)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.context("Failed to checkout parent")?;
if !checkout_status.success() {
println!("{}", "failed".red());
anyhow::bail!("Failed to checkout parent branch");
}
println!("{}", "done".green());
print!("Merging {}... ", current.cyan());
let merge_status = Command::new("git")
.args(["merge", "--squash", ¤t])
.current_dir(workdir)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.context("Failed to merge")?;
if !merge_status.success() {
println!("{}", "failed".red());
let _ = Command::new("git")
.args(["merge", "--abort"])
.current_dir(workdir)
.status();
let _ = Command::new("git")
.args(["reset", "--hard", "HEAD"])
.current_dir(workdir)
.status();
let _ = Command::new("git")
.args(["checkout", ¤t])
.current_dir(workdir)
.status();
anyhow::bail!(
"Failed to merge branch. There may be conflicts.\n\
Restored to branch '{}'.",
current
);
}
println!("{}", "done".green());
print!("Committing... ");
let commit_msg = format!("Fold {} into {}", current, parent);
let commit_status = Command::new("git")
.args(["commit", "-m", &commit_msg])
.current_dir(workdir)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.context("Failed to commit")?;
if !commit_status.success() {
println!("{}", "no changes".yellow());
} else {
println!("{}", "done".green());
}
if !keep_branch {
print!("Deleting {}... ", current.cyan());
let delete_status = Command::new("git")
.args(["branch", "-D", ¤t])
.current_dir(workdir)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.context("Failed to delete branch")?;
if delete_status.success() {
let _ = BranchMetadata::delete(repo.inner(), ¤t);
println!("{}", "done".green());
} else {
println!("{}", "failed".yellow());
}
}
if let Some(parent_meta) = BranchMetadata::read(repo.inner(), parent)? {
let parent_commit = repo.branch_commit(&parent_meta.parent_branch_name)?;
let updated_parent = BranchMetadata {
parent_branch_revision: parent_commit,
..parent_meta
};
updated_parent.write(repo.inner(), parent)?;
}
println!();
println!("{} Folded '{}' into '{}'", "✓".green(), current, parent);
Ok(())
}