use std::ffi::OsString;
use std::path::PathBuf;
use clap::{Args, Parser, Subcommand};
use clap_verbosity_flag::{InfoLevel, Verbosity};
#[derive(Debug, Parser)]
#[clap(
about,
long_about = "An opinionated git worktree workflow for managing multiple branches simultaneously.\n\ngit-workon clones repositories as bare repos with a worktrees-first layout, then provides commands for creating, finding, and cleaning up worktrees — so switching between branches is just `cd`, not `git stash && git checkout`.",
author,
bin_name = env!("CARGO_PKG_NAME"),
propagate_version = true,
version,
)]
pub struct Cli {
#[clap(flatten)]
pub verbose: Verbosity<InfoLevel>,
#[arg(long, global = true, help = "Output results as JSON")]
pub json: bool,
#[arg(long, global = true, help = "Disable color output")]
pub no_color: bool,
#[command(subcommand)]
pub command: Option<Cmd>,
#[clap(flatten)]
pub find: Find,
}
#[derive(Debug, Subcommand)]
pub enum Cmd {
Clone(Clone),
CopyUntracked(CopyUntracked),
#[command(visible_alias = "check")]
Doctor(Doctor),
Find(Find),
Init(Init),
#[command(visible_alias = "ls")]
List(List),
#[command(visible_alias = "mv")]
Move(Move),
New(New),
Prune(Prune),
ShellInit(ShellInit),
#[command(name = "_complete", hide = true)]
Complete(Complete),
#[command(name = "generate-man", hide = true)]
GenerateMan(GenerateMan),
}
#[derive(Debug, Args)]
pub struct Clone {
pub url: String,
pub path: Option<PathBuf>,
#[arg(long, help = "Skip post-create hooks")]
pub no_hooks: bool,
}
#[derive(Debug, Args)]
pub struct CopyUntracked {
pub from: String,
pub to: String,
#[arg(short, long, help = "Override patterns for one-off copy")]
pub pattern: Option<String>,
#[arg(
short = 'x',
long,
help = "Exclude files matching pattern (additive with config)"
)]
pub exclude: Vec<String>,
#[arg(short, long, help = "Overwrite existing files in destination")]
pub force: bool,
#[arg(
long,
help = "Also copy git-ignored files (e.g., .env.local, node_modules)"
)]
pub include_ignored: bool,
}
#[derive(Debug, Args)]
pub struct Init {
pub path: Option<PathBuf>,
#[arg(long, help = "Skip post-create hooks")]
pub no_hooks: bool,
}
#[derive(Debug, Args)]
pub struct List {
#[clap(skip)]
#[allow(dead_code)]
pub json: bool,
#[arg(long, help = "Show only worktrees with uncommitted changes")]
pub dirty: bool,
#[arg(long, help = "Show only worktrees without uncommitted changes")]
pub clean: bool,
#[arg(long, help = "Show only worktrees with unpushed commits")]
pub ahead: bool,
#[arg(long, help = "Show only worktrees behind their upstream")]
pub behind: bool,
#[arg(long, help = "Show only worktrees whose upstream branch is deleted")]
pub gone: bool,
}
#[derive(Debug, Args)]
pub struct Move {
#[arg(num_args = 1..=2, required = true)]
pub names: Vec<String>,
#[arg(short = 'n', long, help = "Preview changes without executing")]
pub dry_run: bool,
#[arg(
short,
long,
help = "Override all safety checks (dirty, unpushed, protected)"
)]
pub force: bool,
}
#[derive(Debug, Args)]
pub struct New {
pub name: Option<String>,
#[arg(short, long, help = "Base branch to branch from")]
pub base: Option<String>,
#[arg(short, long, help = "Create an orphan branch with no parent commits")]
pub orphan: bool,
#[arg(short, long, help = "Detach HEAD in the new working tree")]
pub detach: bool,
#[arg(long, help = "Skip post-create hooks")]
pub no_hooks: bool,
#[arg(
long = "copy-untracked",
overrides_with = "no_copy_untracked",
help = "Copy untracked files from base worktree using configured patterns"
)]
pub copy_untracked: bool,
#[arg(
long = "no-copy-untracked",
overrides_with = "copy_untracked",
help = "Do not copy untracked files (overrides config)"
)]
pub no_copy_untracked: bool,
#[arg(long, help = "Include git-ignored files when copying untracked")]
pub copy_ignored: bool,
#[arg(long, help = "Disable interactive mode (for testing/scripting)")]
pub no_interactive: bool,
#[arg(long, help = "Lock the worktree after creation")]
pub lock: bool,
}
#[derive(Debug, Args)]
pub struct Prune {
#[clap(skip)]
#[allow(dead_code)]
pub json: bool,
pub names: Vec<String>,
#[arg(
short = 'n',
long,
help = "Show what would be pruned without actually removing anything"
)]
pub dry_run: bool,
#[arg(short, long, help = "Skip confirmation prompts")]
pub yes: bool,
#[arg(
long,
help = "Also prune worktrees where the remote tracking branch is gone"
)]
pub gone: bool,
#[arg(
long,
value_name = "BRANCH",
num_args = 0..=1,
default_missing_value = "",
require_equals = false,
help = "Also prune worktrees merged into BRANCH (or default branch)"
)]
pub merged: Option<String>,
#[arg(
long,
help = "Allow pruning worktrees with uncommitted changes (dirty working tree)"
)]
pub allow_dirty: bool,
#[arg(
long,
alias = "allow-unpushed",
help = "Allow pruning worktrees with unmerged commits"
)]
pub allow_unmerged: bool,
#[arg(
short,
long,
help = "Override all safety checks (protection, default branch, dirty, unmerged, locked)"
)]
pub force: bool,
#[arg(long, help = "Keep local branch refs when pruning worktrees")]
pub keep_branch: bool,
#[arg(long, help = "Include locked worktrees when pruning")]
pub include_locked: bool,
}
#[derive(Debug, Args)]
#[command(args_conflicts_with_subcommands = true)]
pub struct Find {
pub name: Option<String>,
#[arg(long, help = "Show only worktrees with uncommitted changes")]
pub dirty: bool,
#[arg(long, help = "Show only worktrees without uncommitted changes")]
pub clean: bool,
#[arg(long, help = "Show only worktrees with unpushed commits")]
pub ahead: bool,
#[arg(long, help = "Show only worktrees behind their upstream")]
pub behind: bool,
#[arg(long, help = "Show only worktrees whose upstream branch is deleted")]
pub gone: bool,
#[arg(long, help = "Disable interactive mode (for testing/scripting)")]
pub no_interactive: bool,
}
#[derive(Debug, Args)]
pub struct Doctor {
#[arg(long)]
pub fix: bool,
#[arg(long)]
pub dry_run: bool,
#[clap(skip)]
#[allow(dead_code)]
pub json: bool,
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum Shell {
Bash,
Zsh,
Fish,
}
#[derive(Debug, Args)]
pub struct ShellInit {
pub shell: Option<Shell>,
#[arg(long, default_value = "workon")]
pub cmd: String,
}
#[derive(Debug, Args)]
pub struct GenerateMan {}
#[derive(Debug, Args)]
pub struct Complete {
#[arg(long, default_value_t = 0)]
pub index: usize,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
pub args: Vec<OsString>,
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_cli() {
Cli::command().debug_assert()
}
}