wsb 0.258.6441

Workspace tools for every project under $PRJ: git across submodules, template stamping, GitHub, and the workbench
Documentation
use crate::fleet::error::{Result, SuperGitError};
use crate::fleet::git::{GitOps, SubmoduleOps, SubmoduleTree};
use crate::fleet::output::Formatter;
use crate::prelude::*;
use dialoguer::Confirm;

pub fn execute<P: AsRef<Path>>(
    repo_root: P,
    branch: &str,
    remote: bool,
    force: bool,
    dry_run: bool,
    verbose: bool,
) -> Result<()> {
    let tree = SubmoduleTree::discover(&repo_root)?;

    Formatter::print_info(&format!(
        "Deleting branch '{}' from superproject + {} submodule(s){}",
        branch,
        tree.len(),
        if remote { " (including remote)" } else { "" }
    ));

    if dry_run {
        Formatter::print_warning("DRY RUN: No changes will be made");
    }

    // Confirmation for remote deletion
    if remote && !dry_run {
        let confirmed = Confirm::new()
            .with_prompt(format!(
                "Are you sure you want to delete '{}' from remote?",
                branch
            ))
            .default(false)
            .interact()
            .unwrap_or(false);

        if !confirmed {
            return Err(SuperGitError::Cancelled);
        }
    }

    let mut successful = 0;
    let mut failed = 0;
    let mut skipped = 0;

    for node in tree.top_down() {
        if verbose {
            Formatter::print_submodule_header(node.label());
        }

        if !node.initialized {
            Formatter::print_warning(&format!(
                "Submodule '{}' not initialized, skipping",
                node.label()
            ));
            skipped += 1;
            continue;
        }

        let repo = match GitOps::open_repo(&node.abs_path) {
            Ok(r) => r,
            Err(e) => {
                Formatter::print_error(&format!("Failed to open '{}': {}", node.label(), e));
                failed += 1;
                continue;
            }
        };

        // A detached HEAD is not on any branch, so it cannot be on the one being
        // deleted; that is distinct from failing to read HEAD at all.
        let is_detached = match GitOps::is_detached_head(&repo) {
            Ok(value) => value,
            Err(e) => {
                Formatter::print_error(&format!(
                    "Failed to check HEAD state of '{}': {}",
                    node.label(),
                    e
                ));
                failed += 1;
                continue;
            }
        };

        if !is_detached {
            let current_branch = match GitOps::current_branch(&repo) {
                Ok(value) => value,
                Err(e) => {
                    Formatter::print_error(&format!(
                        "Failed to determine current branch of '{}': {}",
                        node.label(),
                        e
                    ));
                    failed += 1;
                    continue;
                }
            };

            if current_branch == branch {
                Formatter::print_error(&format!(
                    "Cannot delete '{}' in '{}': currently checked out. Switch to another branch first.",
                    branch,
                    node.label()
                ));
                failed += 1;
                continue;
            }
        }

        // Check if branch exists
        let branch_exists = match GitOps::branch_exists(&repo, branch) {
            Ok(exists) => exists,
            Err(e) => {
                Formatter::print_error(&format!(
                    "Failed to check branch in '{}': {}",
                    node.label(),
                    e
                ));
                failed += 1;
                continue;
            }
        };

        if !branch_exists {
            Formatter::print_warning(&format!(
                "Branch '{}' does not exist in '{}', skipping",
                branch,
                node.label()
            ));
            skipped += 1;
            continue;
        }

        if dry_run {
            Formatter::print_info(&format!("Would delete branch '{}'", branch));
            if remote {
                Formatter::print_info(&format!("Would delete remote branch '{}'", branch));
            }
            successful += 1;
            continue;
        }

        // Delete local branch
        match SubmoduleOps::delete_branch(node, branch, force) {
            Ok(_) => {
                Formatter::print_success(&format!(
                    "Deleted branch '{}' in '{}'",
                    branch,
                    node.label()
                ));
                successful += 1;

                // Delete remote branch if requested
                if remote {
                    match SubmoduleOps::delete_remote_branch(node, branch) {
                        Ok(_) => {
                            Formatter::print_success(&format!(
                                "Deleted remote branch '{}' in '{}'",
                                branch,
                                node.label()
                            ));
                        }
                        Err(e) => {
                            Formatter::print_error(&format!(
                                "Failed to delete remote branch '{}' in '{}': {}",
                                branch,
                                node.label(),
                                e
                            ));
                            failed += 1;
                        }
                    }
                }
            }
            Err(e) => {
                Formatter::print_error(&format!(
                    "Failed to delete branch '{}' in '{}': {}",
                    branch,
                    node.label(),
                    e
                ));
                failed += 1;
            }
        }
    }

    if !dry_run {
        Formatter::print_summary(successful, failed, skipped);
    }

    if failed > 0 {
        return Err(SuperGitError::Other(format!(
            "{} submodule(s) failed to delete branch",
            failed
        )));
    }

    Ok(())
}