stax 0.50.2

Fast stacked Git branches and PRs
Documentation
use crate::engine::BranchMetadata;
use crate::git::GitRepo;
use anyhow::{Context, Result};
use colored::Colorize;
use std::process::Command;

pub fn run() -> Result<()> {
    let repo = GitRepo::open()?;
    let managed_worktrees = repo
        .list_worktrees()?
        .into_iter()
        .filter(|worktree| {
            worktree.path.exists()
                && !worktree.is_prunable
                && worktree
                    .branch
                    .as_deref()
                    .and_then(|branch| BranchMetadata::read(repo.inner(), branch).ok().flatten())
                    .is_some()
        })
        .collect::<Vec<_>>();

    if managed_worktrees.is_empty() {
        println!("{}", "No stax-managed worktrees found.".dimmed());
        return Ok(());
    }

    println!(
        "Restacking {} stax-managed {}...\n",
        managed_worktrees.len(),
        if managed_worktrees.len() == 1 {
            "worktree"
        } else {
            "worktrees"
        }
    );

    let stax_bin = std::env::current_exe().unwrap_or_else(|_| "stax".into());
    let mut ok = 0usize;
    let mut failed = 0usize;

    for worktree in managed_worktrees {
        let branch = worktree
            .branch
            .clone()
            .unwrap_or_else(|| "(detached)".to_string());
        print!("  {} ({}) ... ", worktree.name.cyan(), branch.dimmed());

        let output = Command::new(&stax_bin)
            .args(["restack", "--all", "--quiet", "--yes"])
            .current_dir(&worktree.path)
            .output()
            .with_context(|| format!("Failed to restack worktree '{}'", worktree.name));

        match output {
            Ok(output) if output.status.success() => {
                println!("{}", "ok".green());
                ok += 1;
            }
            Ok(output) => {
                let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
                let first_line = stderr.lines().next().unwrap_or("unknown error");
                println!("{} {}", "failed".red(), first_line.dimmed());
                failed += 1;
            }
            Err(error) => {
                println!("{} {}", "failed".red(), error.to_string().dimmed());
                failed += 1;
            }
        }
    }

    println!();
    println!(
        "Done: {} synced, {} failed",
        ok.to_string().green(),
        if failed > 0 {
            failed.to_string().red().to_string()
        } else {
            failed.to_string().dimmed().to_string()
        }
    );

    if failed > 0 {
        anyhow::bail!("{} worktree(s) failed to restack", failed);
    }

    Ok(())
}