use crate::config::Config;
use crate::engine::{BranchMetadata, Stack};
use crate::git::GitRepo;
use anyhow::{Context, Result};
use colored::Colorize;
use dialoguer::{theme::ColorfulTheme, Confirm, Input};
use std::io::IsTerminal;
use std::process::Command;
pub fn run(
new_name: Option<String>,
edit_message: bool,
push_remote: bool,
literal: bool,
) -> Result<()> {
let is_interactive = std::io::stdin().is_terminal();
let repo = GitRepo::open()?;
let old_name = repo.current_branch()?;
let trunk = repo.trunk_branch()?;
let config = Config::load()?;
let workdir = repo.workdir()?;
if old_name == trunk {
anyhow::bail!("Cannot rename the trunk branch '{}'", trunk);
}
let new_name = match new_name {
Some(name) => {
if literal {
name } else {
config.format_branch_name(&name)
}
}
None => {
if !is_interactive {
anyhow::bail!("New branch name required in non-interactive mode");
}
let input: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("New branch name")
.interact_text()?;
config.format_branch_name(&input)
}
};
if new_name == old_name {
println!("Branch name unchanged.");
return Ok(());
}
if repo.branch_commit(&new_name).is_ok() {
anyhow::bail!("Branch '{}' already exists", new_name);
}
let stack = Stack::load(&repo)?;
let status = Command::new("git")
.args(["branch", "-m", &old_name, &new_name])
.current_dir(workdir)
.status()
.context("Failed to rename branch")?;
if !status.success() {
anyhow::bail!(
"Failed to rename branch from '{}' to '{}'",
old_name,
new_name
);
}
println!(
"✓ Renamed branch '{}' → '{}'",
old_name.bright_black(),
new_name.green()
);
if let Some(meta) = BranchMetadata::read(repo.inner(), &old_name)? {
meta.write(repo.inner(), &new_name)?;
crate::git::refs::delete_metadata(repo.inner(), &old_name)?;
}
for (child_name, child_info) in &stack.branches {
if child_info.parent.as_deref() == Some(&old_name) {
if let Some(mut meta) = BranchMetadata::read(repo.inner(), child_name)? {
meta.parent_branch_name = new_name.clone();
meta.write(repo.inner(), child_name)?;
println!(
" Updated child '{}' to reference new parent",
child_name.cyan()
);
}
}
}
let remote_name = config.remote_name();
let remote_branches =
crate::remote::get_remote_branches(workdir, remote_name).unwrap_or_default();
if remote_branches.contains(&old_name) {
let should_push = if push_remote {
true } else if is_interactive {
Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(format!(
"Push '{}' and delete old remote '{}'?",
new_name, old_name
))
.default(true)
.interact()?
} else {
false
};
if should_push {
print!(" Pushing {}... ", new_name.cyan());
std::io::Write::flush(&mut std::io::stdout()).ok();
let push_status = Command::new("git")
.args(["push", "-u", remote_name, &new_name])
.current_dir(workdir)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
if push_status.map(|s| s.success()).unwrap_or(false) {
println!("{}", "✓".green());
} else {
println!("{}", "failed".red());
}
print!(" Deleting remote {}... ", old_name.bright_black());
std::io::Write::flush(&mut std::io::stdout()).ok();
let delete_status = Command::new("git")
.args(["push", remote_name, "--delete", &old_name])
.current_dir(workdir)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
if delete_status.map(|s| s.success()).unwrap_or(false) {
println!("{}", "✓".green());
} else {
println!("{}", "failed".red());
}
}
}
let should_edit = if edit_message {
true
} else if is_interactive {
Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Edit the commit message?")
.default(false)
.interact()?
} else {
false
};
if should_edit {
let status = Command::new("git")
.args(["commit", "--amend"])
.current_dir(workdir)
.status()
.context("Failed to amend commit")?;
if status.success() {
println!("✓ Commit message updated");
}
}
println!();
println!(
"Branch renamed: {} → {}",
old_name.bright_black(),
new_name.green().bold()
);
Ok(())
}