use std::path::PathBuf;
use clap::{Args, Parser, Subcommand, ValueEnum};
#[derive(Parser, Debug)]
#[command(
name = "limb",
version,
about = "A polished CLI + TUI for working with git worktrees",
long_about = "limb makes git worktree pleasant to use. Zero-config inside any git repo; \
progressive configuration when you want templates, hooks, or cross-repo mode.",
propagate_version = true,
disable_help_subcommand = true
)]
#[allow(clippy::struct_excessive_bools)]
pub struct Cli {
#[arg(
long,
short = 'r',
global = true,
env = "LIMB_REPO",
value_name = "PATH",
help = "Target git repository (default: discovered from cwd)"
)]
pub repo: Option<PathBuf>,
#[arg(
long,
global = true,
env = "LIMB_CONFIG",
value_name = "PATH",
help = "Path to a specific config file (overrides ~/.config/limb/config.toml)"
)]
pub config: Option<PathBuf>,
#[arg(
long,
global = true,
help = "Emit machine-readable JSON where applicable"
)]
pub json: bool,
#[arg(
long,
global = true,
env = "NO_COLOR",
help = "Disable colored output (NO_COLOR env also honored)"
)]
pub no_color: bool,
#[arg(
long,
short = 'y',
global = true,
help = "Skip interactive confirmation prompts"
)]
pub yes: bool,
#[arg(
long,
short = 'q',
global = true,
help = "Suppress non-essential informational output"
)]
pub quiet: bool,
#[command(subcommand)]
pub cmd: Cmd,
}
#[derive(Subcommand, Debug)]
pub enum Cmd {
#[command(about = "List worktrees in the current repo")]
List(ListArgs),
#[command(about = "Create a new worktree")]
Add(AddArgs),
#[command(about = "Remove a worktree")]
Remove(RemoveArgs),
#[command(about = "Print the path of a worktree (for shell eval)")]
Cd(CdArgs),
#[command(about = "Interactive TUI picker. Prints selected path on exit")]
Pick,
#[command(about = "Per-worktree status table (dirty, ahead/behind, upstream)")]
Status(StatusArgs),
#[command(about = "Fetch and fast-forward worktrees")]
Update(UpdateArgs),
#[command(about = "Lock a worktree so it cannot be pruned or removed")]
Lock(LockArgs),
#[command(about = "Unlock a previously-locked worktree")]
Unlock(UnlockArgs),
#[command(about = "Repair worktree administrative files after manual moves")]
Repair(RepairArgs),
#[command(about = "Remove administrative files for worktrees that no longer exist")]
Prune(PruneArgs),
#[command(about = "Rename a worktree. Move its directory and update git refs")]
Rename(RenameArgs),
#[command(about = "Remove worktrees whose upstream branches are gone from origin")]
Clean(CleanArgs),
#[command(about = "Diagnose setup and emit actionable fixes")]
Doctor,
#[command(about = "Print the resolved configuration as JSON")]
Config,
#[command(about = "Link shared files into every worktree of the current repo")]
Setup(SetupArgs),
#[command(about = "Convert a plain-clone repo into the bare-clone + worktrees layout")]
Migrate(MigrateArgs),
#[command(about = "Emit shell integration (eval in your rc)")]
Init(InitArgs),
#[command(about = "Emit shell completions")]
Completions(CompletionsArgs),
#[command(
about = "Mark a path to be cd'd into on the shell's next prompt (tmux only)",
hide = true
)]
MarkCd(MarkCdArgs),
}
#[derive(Args, Debug)]
pub struct ListArgs {
#[arg(
long,
help = "Include worktrees from every repo under configured projects.roots"
)]
pub all: bool,
#[arg(
long,
short = 'v',
help = "Show lock and prune reasons next to the worktree tag"
)]
pub verbose: bool,
}
#[derive(Args, Debug)]
pub struct StatusArgs {
#[arg(
long,
help = "Include worktrees from every repo under configured projects.roots"
)]
pub all: bool,
}
#[derive(Args, Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct AddArgs {
#[arg(value_name = "NAME", help = "Worktree name (or template slug with -t)")]
pub name: String,
#[arg(
value_name = "BASE",
conflicts_with_all = ["from", "orphan"],
help = "Commit-ish to branch from (shortcut for --from)"
)]
pub base: Option<String>,
#[arg(
long,
short = 'b',
value_name = "BRANCH",
conflicts_with_all = ["detach", "orphan", "force_branch"],
help = "Check out this existing branch in the new worktree"
)]
pub branch: Option<String>,
#[arg(
long,
short = 'B',
value_name = "BRANCH",
requires = "from",
conflicts_with_all = ["branch", "detach", "orphan", "template"],
help = "Force-create a new branch (overwrites if it exists; requires --from)"
)]
pub force_branch: Option<String>,
#[arg(
long,
value_name = "COMMIT",
conflicts_with = "orphan",
help = "Create a new branch starting from this commit-ish"
)]
pub from: Option<String>,
#[arg(
long,
requires = "from",
conflicts_with_all = ["no_track", "detach", "orphan"],
help = "Configure upstream tracking for the new branch (requires --from)"
)]
pub track: bool,
#[arg(
long = "no-track",
requires = "from",
conflicts_with_all = ["track", "detach", "orphan"],
help = "Disable upstream tracking config for the new branch (requires --from)"
)]
pub no_track: bool,
#[arg(
long,
requires = "from",
conflicts_with_all = ["no_guess_remote", "detach", "orphan"],
help = "Guess the upstream remote-tracking branch from the start commit (requires --from)"
)]
pub guess_remote: bool,
#[arg(
long = "no-guess-remote",
requires = "from",
conflicts_with_all = ["guess_remote", "detach", "orphan"],
help = "Disable upstream guessing for the new branch (requires --from)"
)]
pub no_guess_remote: bool,
#[arg(
long,
conflicts_with = "orphan",
help = "Skip checking files out into the new worktree"
)]
pub no_checkout: bool,
#[arg(
long,
conflicts_with = "no_relative_paths",
help = "Store admin paths relative to the repo (portable across mounts)"
)]
pub relative_paths: bool,
#[arg(
long = "no-relative-paths",
conflicts_with = "relative_paths",
help = "Force absolute admin paths (override config)"
)]
pub no_relative_paths: bool,
#[arg(
long,
short = 't',
value_name = "TEMPLATE",
conflicts_with_all = ["detach", "orphan"],
help = "Apply a named template from .limb.toml"
)]
pub template: Option<String>,
#[arg(
long,
conflicts_with = "orphan",
help = "Create a detached HEAD (no branch)"
)]
pub detach: bool,
#[arg(long, help = "Create an orphan branch with no parent commits")]
pub orphan: bool,
#[arg(long, help = "Lock the worktree after creation")]
pub lock: bool,
#[arg(
long,
value_name = "TEXT",
requires = "lock",
help = "Reason for locking (requires --lock)"
)]
pub reason: Option<String>,
#[arg(long, help = "Show what would be added without making changes")]
pub dry_run: bool,
}
#[derive(Args, Debug)]
pub struct RemoveArgs {
#[arg(value_name = "NAME", help = "Worktree to remove")]
pub name: String,
#[arg(long, short = 'f', help = "Skip the confirm prompt")]
pub force: bool,
#[arg(long, help = "Show what would be removed without making changes")]
pub dry_run: bool,
}
#[derive(Args, Debug)]
pub struct CdArgs {
#[arg(value_name = "NAME", help = "Worktree name")]
pub name: String,
}
#[derive(Args, Debug)]
pub struct LockArgs {
#[arg(value_name = "NAME", help = "Worktree to lock")]
pub name: String,
#[arg(
long,
value_name = "TEXT",
help = "Reason for locking (shown in `git worktree list`)"
)]
pub reason: Option<String>,
#[arg(long, help = "Show what would be locked without making changes")]
pub dry_run: bool,
}
#[derive(Args, Debug)]
pub struct UnlockArgs {
#[arg(value_name = "NAME", help = "Worktree to unlock")]
pub name: String,
#[arg(long, help = "Show what would be unlocked without making changes")]
pub dry_run: bool,
}
#[derive(Args, Debug)]
pub struct RepairArgs {
#[arg(
value_name = "PATH",
help = "Specific worktree path(s) to repair; defaults to every worktree"
)]
pub paths: Vec<PathBuf>,
}
#[derive(Args, Debug)]
pub struct PruneArgs {
#[arg(
long,
value_name = "WHEN",
help = "Only prune entries older than this (e.g. '1.week.ago', '2023-01-01')"
)]
pub expire: Option<String>,
#[arg(long, help = "Show what would be pruned without making changes")]
pub dry_run: bool,
#[arg(long, help = "Report what is pruned")]
pub report: bool,
}
#[derive(Args, Debug)]
pub struct RenameArgs {
#[arg(value_name = "OLD", help = "Current worktree name")]
pub old: String,
#[arg(value_name = "NEW", help = "New worktree name")]
pub new: String,
#[arg(long, help = "Show what would be renamed without making changes")]
pub dry_run: bool,
}
#[derive(Args, Debug)]
pub struct CleanArgs {
#[arg(long, help = "Show what would be removed without making changes")]
pub dry_run: bool,
}
#[derive(Args, Debug)]
pub struct SetupArgs {
#[arg(
long,
help = "Recreate symlinks that point elsewhere (preserves regular files)"
)]
pub refresh_shared: bool,
#[arg(long, help = "Show what would be done without making changes")]
pub dry_run: bool,
}
#[derive(Args, Debug)]
pub struct MigrateArgs {
#[arg(
value_name = "REPO",
help = "Path to a plain git clone to convert (default: current repo)"
)]
pub repo: Option<PathBuf>,
#[arg(long, help = "Show what would be done without making changes")]
pub dry_run: bool,
}
#[derive(Args, Debug)]
pub struct UpdateArgs {
#[arg(
long,
conflicts_with = "ff_only",
help = "Only fetch. Don't fast-forward"
)]
pub fetch_only: bool,
#[arg(long, help = "Only fast-forward. Skip fetch")]
pub ff_only: bool,
#[arg(long, help = "Show what would be done without making changes")]
pub dry_run: bool,
}
#[derive(Args, Debug)]
pub struct InitArgs {
#[arg(value_enum, help = "Target shell")]
pub shell: Shell,
#[arg(
long,
value_name = "PREFIX",
default_value = "gw",
help = "Prefix for emitted wrapper functions (gw, gwa, gwp, ...)"
)]
pub prefix: String,
}
#[derive(Args, Debug)]
pub struct MarkCdArgs {
#[arg(value_name = "PATH", help = "Path for the shell to cd into")]
pub path: PathBuf,
}
#[derive(Args, Debug)]
pub struct CompletionsArgs {
#[arg(value_enum, help = "Target shell")]
pub shell: CompletionShell,
}
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Shell {
Zsh,
Bash,
Fish,
Pwsh,
}
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompletionShell {
Zsh,
Bash,
Fish,
Pwsh,
Elvish,
}
impl From<CompletionShell> for clap_complete::Shell {
fn from(s: CompletionShell) -> Self {
match s {
CompletionShell::Zsh => Self::Zsh,
CompletionShell::Bash => Self::Bash,
CompletionShell::Fish => Self::Fish,
CompletionShell::Pwsh => Self::PowerShell,
CompletionShell::Elvish => Self::Elvish,
}
}
}