limb 0.1.0

A focused CLI for git worktree management
Documentation
//! limb. A polished CLI + TUI for working with git worktrees.
//!
//! The crate is organised as a thin command dispatcher on top of a handful of
//! focused modules:
//!
//! - [`cli`]. Clap argument definitions for every subcommand.
//! - [`cmd`]. One submodule per subcommand; each exposes `run(ctx, args)`.
//! - [`worktree`]. The [`worktree::Worktree`] data model and the git-worktree
//!   wrappers every subcommand builds on.
//! - [`config`]. TOML loading for per-repo `.limb.toml` and
//!   `~/.config/limb/config.toml`.
//! - [`context`]. Runtime state (repo, config, flags) passed to every
//!   subcommand.
//! - [`discover`], [`hooks`], [`shell`]. Cross-repo scanning, user-hook
//!   execution, and shell-integration emission respectively.
//! - [`tui`]. The interactive picker (`limb pick`), backed by `ratatui`.
//!
//! Errors bubble up via [`anyhow::Result`]; [`error::Canceled`] is the one
//! structural error, used to signal Ctrl-C without an error message.

pub mod cancel;
pub mod cli;
pub mod cmd;
pub mod config;
pub mod context;
pub mod discover;
pub mod error;
pub mod fmt;
pub mod fuzzy;
pub mod git;
pub mod hooks;
pub mod shell;
pub mod style;
pub mod tui;
pub mod worktree;

use std::process::ExitCode;

use clap::Parser;

use crate::cancel::Cancel;
use crate::cli::{Cli, Cmd};
use crate::context::Context;

const EXIT_CANCELED: u8 = 130;

/// Runs the limb CLI to completion.
///
/// Parses arguments from `std::env::args`, installs Ctrl-C handling,
/// dispatches to the selected subcommand, and translates errors into the
/// appropriate process exit code.
#[allow(unsafe_code)]
#[must_use]
pub fn run() -> ExitCode {
    let cli = Cli::parse();
    if cli.no_color {
        // SAFETY: single-threaded at startup before any cmd dispatch.
        unsafe {
            std::env::set_var("NO_COLOR", "1");
        }
    }
    match dispatch(&cli) {
        Ok(()) => ExitCode::SUCCESS,
        Err(e) if e.downcast_ref::<error::Canceled>().is_some() => ExitCode::from(EXIT_CANCELED),
        Err(e) => {
            let s = style::ERROR;
            anstream::eprintln!("{s}error:{s:#} {e:#}");
            ExitCode::FAILURE
        }
    }
}

fn dispatch(cli: &Cli) -> anyhow::Result<()> {
    let cancel = Cancel::install();
    let ctx = Context::from_cli(cli, cancel.clone());
    let result = dispatch_cmd(&ctx, cli);
    if cancel.is_set() && result.is_err() {
        return Err(error::Canceled.into());
    }
    result
}

fn dispatch_cmd(ctx: &Context, cli: &Cli) -> anyhow::Result<()> {
    match &cli.cmd {
        Cmd::List(args) => cmd::list::run(ctx, args),
        Cmd::Add(args) => cmd::add::run(ctx, args),
        Cmd::Remove(args) => cmd::remove::run(ctx, args),
        Cmd::Cd(args) => cmd::cd::run(ctx, args),
        Cmd::Pick => cmd::pick::run(ctx),
        Cmd::Status(args) => cmd::status::run(ctx, args),
        Cmd::Update(args) => cmd::update::run(ctx, args),
        Cmd::Lock(args) => cmd::lock::run(ctx, args),
        Cmd::Unlock(args) => cmd::unlock::run(ctx, args),
        Cmd::Repair(args) => cmd::repair::run(ctx, args),
        Cmd::Prune(args) => cmd::prune::run(ctx, args),
        Cmd::Rename(args) => cmd::rename::run(ctx, args),
        Cmd::Clean(args) => cmd::clean::run(ctx, args),
        Cmd::Doctor => cmd::doctor::run(ctx),
        Cmd::Config => cmd::config::run(ctx),
        Cmd::Setup(args) => cmd::setup::run(ctx, args),
        Cmd::Migrate(args) => cmd::migrate::run(ctx, args),
        Cmd::Init(args) => cmd::init::run(args),
        Cmd::Completions(args) => cmd::completions::run(args),
        Cmd::MarkCd(args) => cmd::mark_cd::run(args),
    }
}