wsb 0.275.7622

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::git_backend;
use crate::prelude::*;

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

    Formatter::print_info(&format!(
        "Staging all changes in superproject + {} submodule(s)",
        tree.len()
    ));

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

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

    // Deepest first, superproject last. Staging a repository records the current
    // gitlink of each of its submodules, so a child must be staged before the
    // parent that points at it.
    for node in tree.bottom_up() {
        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;
            }
        };

        let is_clean = match GitOps::is_clean(&repo) {
            Ok(c) => c,
            Err(e) => {
                Formatter::print_error(&format!(
                    "Failed to check status of '{}': {}",
                    node.label(),
                    e
                ));
                failed += 1;
                continue;
            }
        };

        if is_clean {
            if verbose {
                Formatter::print_info(&format!("No changes in '{}'", node.label()));
            }
            skipped += 1;
            continue;
        }

        if dry_run {
            let files = GitOps::get_uncommitted_files(&repo)?;
            Formatter::print_info(&format!(
                "Would stage {} file(s) in '{}'",
                files.len(),
                node.label()
            ));
            successful += 1;
            continue;
        }

        match SubmoduleOps::stage(node) {
            Ok(_) => {
                Formatter::print_success(&format!("Staged changes in '{}'", node.label()));
                successful += 1;
            }
            Err(e) => {
                Formatter::print_error(&format!("Failed to stage in '{}': {}", node.label(), e));
                failed += 1;
            }
        }
    }

    // Stage superproject
    if verbose {
        Formatter::print_submodule_header("[SUPERPROJECT]");
    }

    if !dry_run {
        let repo = git_backend::open_repo(repo_root.as_ref())?;
        match git_backend::stage_all(&repo) {
            Ok(()) => {
                Formatter::print_success("Staged changes in superproject");
                successful += 1;
            }
            Err(err) => {
                Formatter::print_error(&format!("Failed to stage superproject: {}", err));
                failed += 1;
            }
        }
    } else {
        Formatter::print_info("Would stage changes in superproject");
        successful += 1;
    }

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

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

    Ok(())
}