stax 0.29.4

Fast stacked Git branches and PRs
Documentation
use crate::config::Config;
use crate::engine::Stack;
use crate::git::GitRepo;
use crate::remote;
use anyhow::Result;
use colored::Colorize;

pub fn run() -> Result<()> {
    println!("{}", "stax doctor".bold());
    println!();

    let repo = match GitRepo::open() {
        Ok(repo) => repo,
        Err(err) => {
            println!("{} {}", "".red(), err);
            return Ok(());
        }
    };

    let config = Config::load()?;
    let mut issues = 0;

    if repo.is_initialized() {
        println!("{} {}", "".green(), "Repo initialized".dimmed());
    } else {
        println!(
            "{} {}",
            "".red(),
            "Repo not initialized (run `stax` once)".yellow()
        );
        issues += 1;
    }

    match repo.trunk_branch() {
        Ok(trunk) => println!("{} {} {}", "".green(), "Trunk:".dimmed(), trunk.cyan()),
        Err(err) => {
            println!("{} {} {}", "".red(), "Trunk not set:".yellow(), err);
            issues += 1;
        }
    }

    let remote_name = config.remote_name();
    match remote::get_remote_url(repo.workdir()?, remote_name) {
        Ok(url) => println!(
            "{} {} {}",
            "".green(),
            "Remote:".dimmed(),
            format!("{} ({})", remote_name, url).cyan()
        ),
        Err(err) => {
            println!("{} {} {}", "".red(), "Remote missing:".yellow(), err);
            issues += 1;
        }
    }

    if Config::github_token().is_some() {
        println!("{} {}", "".green(), "GitHub auth available".dimmed());
    } else {
        println!(
            "{} {}",
            "".yellow(),
            "GitHub auth missing (PR creation disabled)".yellow()
        );
    }

    if repo.is_dirty()? {
        println!("{} {}", "".yellow(), "Working tree is dirty".yellow());
    } else {
        println!("{} {}", "".green(), "Working tree clean".dimmed());
    }

    if repo.rebase_in_progress()? {
        println!(
            "{} {}",
            "".yellow(),
            "Rebase in progress (run `stax continue`)".yellow()
        );
    }

    if let Ok(stack) = Stack::load(&repo) {
        let mut orphaned = Vec::new();
        for (name, info) in &stack.branches {
            if let Some(parent) = &info.parent {
                if repo.branch_commit(parent).is_err() {
                    orphaned.push((name.clone(), parent.clone()));
                }
            }
        }

        if !orphaned.is_empty() {
            issues += 1;
            println!(
                "{} {}",
                "".red(),
                "Branches with missing parents:".yellow()
            );
            for (branch, parent) in orphaned {
                println!("  {}{}", branch, parent);
            }
        }

        let needs_restack = stack.needs_restack();
        if !needs_restack.is_empty() {
            println!(
                "{} {}",
                "".yellow(),
                format!(
                    "{} {} need restack",
                    needs_restack.len(),
                    if needs_restack.len() == 1 {
                        "branch"
                    } else {
                        "branches"
                    }
                )
                .yellow()
            );
        }
    }

    println!();
    if issues == 0 {
        println!("{}", "✓ Doctor check complete (no critical issues)".green());
    } else {
        println!("{}", format!("✗ Doctor found {} issue(s)", issues).yellow());
    }

    Ok(())
}