workbloom 0.9.0

A Git worktree management tool with automatic file copying
Documentation
use anyhow::Result;
use clap::{Parser, Subcommand};

use workbloom::commands::{cleanup, setup};
use workbloom::output;

#[derive(Parser)]
#[command(
    author,
    version = concat!(env!("CARGO_PKG_VERSION"), " (", env!("GIT_HASH"), ")"),
    about,
    long_about = None
)]
#[command(propagate_version = true)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    #[command(
        about = "Set up a new git worktree with automatic file copying",
        visible_alias = "s"
    )]
    Setup {
        #[arg(help = "The branch name for the worktree")]
        branch_name: String,

        #[arg(long, conflicts_with_all = &["no_shell", "print_path"], help = "Start a new shell in the worktree directory")]
        shell: bool,

        #[arg(
            long,
            conflicts_with = "print_path",
            help = "Skip starting a new shell and print human-friendly output (legacy)"
        )]
        no_shell: bool,

        #[arg(
            long,
            alias = "no-tmux",
            help = "Disable terminal multiplexer session management when starting a shell"
        )]
        no_mux: bool,

        #[arg(
            long,
            conflicts_with = "no_shell",
            help = "Print only the worktree path to stdout (default)"
        )]
        print_path: bool,
    },

    #[command(about = "Clean up worktrees", visible_alias = "c")]
    Cleanup {
        #[arg(long, conflicts_with_all = &["pattern", "interactive", "status"], help = "Remove only merged worktrees")]
        merged: bool,

        #[arg(long, value_name = "PATTERN", conflicts_with_all = &["merged", "interactive", "status"], help = "Remove worktrees matching pattern")]
        pattern: Option<String>,

        #[arg(long, conflicts_with_all = &["merged", "pattern", "status"], help = "Interactive removal")]
        interactive: bool,

        #[arg(long, conflicts_with_all = &["merged", "pattern", "interactive"], help = "Show merge status of all branches")]
        status: bool,

        #[arg(
            long,
            help = "Force cleanup without remote branch checks (use with --merged). Still protects recently created worktrees"
        )]
        force: bool,
    },
}

fn main() -> Result<()> {
    colored::control::set_override(should_use_color());

    let cli = Cli::parse();

    match cli.command {
        Commands::Setup {
            branch_name,
            shell,
            no_shell,
            no_mux,
            print_path,
        } => {
            let start_shell = shell;
            let print_path = print_path || (!shell && !no_shell);
            output::set_machine_output(print_path);
            setup::execute(&branch_name, start_shell, !no_mux, print_path)?;
        }
        Commands::Cleanup {
            merged,
            pattern,
            interactive,
            status,
            force,
        } => {
            let mode = if merged || (pattern.is_none() && !interactive && !status) {
                cleanup::CleanupMode::Merged { force }
            } else if let Some(p) = pattern {
                cleanup::CleanupMode::Pattern(p)
            } else if interactive {
                cleanup::CleanupMode::Interactive
            } else {
                cleanup::CleanupMode::Status
            };

            cleanup::execute(mode)?;
        }
    }

    Ok(())
}

fn should_use_color() -> bool {
    if std::env::var("NO_COLOR").is_ok()
        || std::env::var("CLICOLOR").map(|v| v == "0").unwrap_or(false)
    {
        return false;
    }
    true
}