use std::path::Path;
use worktrunk::HookType;
use worktrunk::config::UserConfig;
use worktrunk::git::{BranchDeletionMode, Repository};
use worktrunk::styling::{eprintln, info_message};
use super::resolve::path_mismatch;
use super::types::RemoveResult;
use crate::commands::command_executor::CommandContext;
use crate::commands::context::CommandEnv;
use crate::commands::hooks::HookAnnouncer;
use crate::commands::repository_ext::{
check_not_default_branch, compute_integration_reason, is_primary_worktree,
};
use crate::commands::template_vars::TemplateVars;
pub struct FinishAfterMergeArgs<'a> {
pub current_branch: &'a str,
pub target_branch: &'a str,
pub target_worktree_path: Option<&'a Path>,
pub remove: bool,
pub verify: bool,
pub yes: bool,
}
pub fn finish_after_merge(
repo: &Repository,
config: &UserConfig,
env: &CommandEnv,
announcer: &mut HookAnnouncer<'_>,
args: FinishAfterMergeArgs<'_>,
) -> anyhow::Result<bool> {
let FinishAfterMergeArgs {
current_branch,
target_branch,
target_worktree_path,
remove,
verify,
yes,
} = args;
let on_target = current_branch == target_branch;
let destination_path = match target_worktree_path {
Some(path) => path.to_path_buf(),
None => repo.home_path()?,
};
let mut feature_vars = TemplateVars::new().with_active_worktree(&env.worktree_path);
let feature_commit = if verify || remove {
repo.current_worktree()
.run_command(&["rev-parse", "HEAD"])
.ok()
.map(|s| s.trim().to_string())
} else {
None
};
if let Some(commit) = feature_commit.as_deref() {
let short = repo
.short_sha(commit)
.unwrap_or_else(|_| commit.to_string());
feature_vars = feature_vars.with_active_commit(commit, &short);
}
let removed = if !remove {
eprintln!("{}", info_message("Worktree preserved (--no-remove)"));
false
} else if on_target {
eprintln!(
"{}",
info_message("Worktree preserved (already on target branch)")
);
false
} else if is_primary_worktree(repo)? {
eprintln!("{}", info_message("Worktree preserved (primary worktree)"));
false
} else {
check_not_default_branch(repo, current_branch, &BranchDeletionMode::SafeDelete)?;
let current_wt = repo.current_worktree();
current_wt.ensure_clean("remove worktree after merge", Some(current_branch), false)?;
let worktree_root = current_wt.root()?;
let snapshot = repo.capture_refs()?;
let (integration_reason, _) = compute_integration_reason(
repo,
&snapshot,
Some(current_branch),
Some(target_branch),
BranchDeletionMode::SafeDelete,
);
let expected_path = path_mismatch(repo, current_branch, &worktree_root, config);
let remove_result = RemoveResult::RemovedWorktree {
main_path: destination_path.clone(),
worktree_path: worktree_root,
changed_directory: true,
branch_name: Some(current_branch.to_string()),
deletion_mode: BranchDeletionMode::SafeDelete,
target_branch: Some(target_branch.to_string()),
integration_reason,
force_worktree: false,
expected_path,
removed_commit: feature_commit.clone(),
};
crate::output::handle_remove_output(&remove_result, false, verify, false, announcer)?;
true
};
if verify {
let ctx = CommandContext::new(repo, config, Some(current_branch), &destination_path, yes);
let display_path = if removed {
crate::output::post_hook_display_path(&destination_path)
} else {
crate::output::pre_hook_display_path(&destination_path)
};
let mut vars = feature_vars.with_target(target_branch);
if let Some(p) = target_worktree_path {
vars = vars.with_target_worktree_path(p);
}
announcer.register(
&ctx,
HookType::PostMerge,
&vars.as_extra_vars(),
display_path,
)?;
}
Ok(removed)
}