use crate::style::run_hook_if_exists;
pub(crate) async fn cmd_rebase(
branch: &str,
interactive: bool,
resume: bool,
abort: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let mut repo = suture_core::repository::Repository::open(std::path::Path::new("."))?;
if abort {
repo.rebase_abort()?;
println!("Rebase aborted.");
return Ok(());
}
if resume {
eprintln!(
"Note: --continue is not yet needed. After editing during rebase, run `suture commit` then `suture rebase --continue`."
);
return Ok(());
}
if interactive {
return cmd_rebase_interactive(&mut repo, branch).await;
}
let (current_branch, head_id) = repo
.head()
.unwrap_or_else(|_| ("main".to_string(), suture_common::Hash::ZERO));
let mut pre_extra = std::collections::HashMap::new();
pre_extra.insert("SUTURE_BRANCH".to_string(), current_branch.clone());
pre_extra.insert("SUTURE_HEAD".to_string(), head_id.to_hex());
pre_extra.insert("SUTURE_REBASE_ONTO".to_string(), branch.to_string());
run_hook_if_exists(repo.root(), "pre-rebase", pre_extra)?;
let result = repo.rebase(branch)?;
if result.patches_replayed > 0 {
println!(
"Rebase onto '{}': {} patch(es) replayed",
branch, result.patches_replayed
);
} else {
println!("Already up to date.");
}
let (branch_after, head_after) = repo.head()?;
let mut post_extra = std::collections::HashMap::new();
post_extra.insert("SUTURE_BRANCH".to_string(), branch_after);
post_extra.insert("SUTURE_HEAD".to_string(), head_after.to_hex());
post_extra.insert("SUTURE_REBASE_ONTO".to_string(), branch.to_string());
run_hook_if_exists(repo.root(), "post-rebase", post_extra)?;
Ok(())
}
async fn cmd_rebase_interactive(
repo: &mut suture_core::repository::Repository,
base_branch: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let base_bn = suture_common::BranchName::new(base_branch)
.map_err(|e| format!("invalid branch name '{}': {}", base_branch, e))?;
let base_id = repo.dag().get_branch(&base_bn).ok_or_else(|| {
let branches: Vec<String> = repo.list_branches().into_iter().map(|(n, _)| n).collect();
if let Some(suggestion) = crate::fuzzy::suggest(base_branch, &branches) {
format!(
"branch '{}' not found (did you mean '{}'?)",
base_branch, suggestion
)
} else {
format!("branch '{}' not found", base_branch)
}
})?;
let todo_content = repo.generate_rebase_todo(&base_id)?;
if todo_content
.lines()
.filter(|l| !l.trim().is_empty() && !l.starts_with('#'))
.count()
== 0
{
println!("Nothing to rebase.");
return Ok(());
}
let todo_path = std::env::temp_dir().join("suture-rebase-todo");
std::fs::write(&todo_path, &todo_content)?;
let editor = std::env::var("SUTURE_EDITOR")
.or_else(|_| std::env::var("EDITOR"))
.unwrap_or_else(|_| "vim".to_string());
let status = std::process::Command::new(&editor)
.arg(&todo_path)
.status()
.map_err(|e| format!("failed to run editor '{}': {}", editor, e))?;
if !status.success() {
std::fs::remove_file(&todo_path).ok();
return Err(format!("editor '{}' exited with non-zero status", editor).into());
}
let edited = std::fs::read_to_string(&todo_path)?;
std::fs::remove_file(&todo_path).ok();
let has_entries = edited
.lines()
.any(|l| !l.trim().is_empty() && !l.trim().starts_with('#'));
if !has_entries {
println!("Rebase cancelled (no commits selected).");
return Ok(());
}
let plan = repo.parse_rebase_todo(&edited, &base_id)?;
let pick_count = plan
.entries
.iter()
.filter(|e| e.action == suture_core::repository::RebaseAction::Pick)
.count();
let drop_count = plan
.entries
.iter()
.filter(|e| e.action == suture_core::repository::RebaseAction::Drop)
.count();
let squash_count = plan
.entries
.iter()
.filter(|e| e.action == suture_core::repository::RebaseAction::Squash)
.count();
let reword_count = plan
.entries
.iter()
.filter(|e| e.action == suture_core::repository::RebaseAction::Reword)
.count();
println!(
"Rebase plan: {} pick, {} squash, {} reword, {} drop",
pick_count, squash_count, reword_count, drop_count
);
let (current_branch, head_id) = repo
.head()
.unwrap_or_else(|_| ("main".to_string(), suture_common::Hash::ZERO));
let mut pre_extra = std::collections::HashMap::new();
pre_extra.insert("SUTURE_BRANCH".to_string(), current_branch.clone());
pre_extra.insert("SUTURE_HEAD".to_string(), head_id.to_hex());
pre_extra.insert("SUTURE_REBASE_ONTO".to_string(), base_branch.to_string());
run_hook_if_exists(repo.root(), "pre-rebase", pre_extra)?;
let new_tip = repo.rebase_interactive(&plan, &base_id)?;
let (branch_after, head_after) = repo.head()?;
let mut post_extra = std::collections::HashMap::new();
post_extra.insert("SUTURE_BRANCH".to_string(), branch_after);
post_extra.insert("SUTURE_HEAD".to_string(), head_after.to_hex());
post_extra.insert("SUTURE_REBASE_ONTO".to_string(), base_branch.to_string());
run_hook_if_exists(repo.root(), "post-rebase", post_extra)?;
println!("Interactive rebase complete. New tip: {}", new_tip);
Ok(())
}