wsb 0.258.6441

Workspace tools for every project under $PRJ: git across submodules, template stamping, GitHub, and the workbench
Documentation
use crate::prelude::*;
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "gitfleet")]
#[command(about = "Git operations over the active git scope", long_about = None)]
#[command(version)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Commands,

    /// Enable verbose output
    #[arg(short, long, global = true)]
    pub verbose: bool,

    /// Dry run mode (show what would happen without executing)
    #[arg(short = 'n', long, global = true)]
    pub dry_run: bool,
}

#[derive(Subcommand, Debug)]
pub enum Commands {
    /// Show status of the active git scope
    Status,

    /// Find every worktree with local changes and, after confirmation, throw
    /// those changes away
    Discard {
        /// Delete untracked files too. They are the ones git holds no copy of,
        /// so this cannot be undone by anything
        #[arg(long)]
        untracked: bool,

        /// Do not ask. For a script that has already decided
        #[arg(long)]
        yes: bool,
    },

    /// Checkout a branch in the active git scope
    Checkout {
        /// Branch name to checkout
        branch: String,

        /// Create the branch if it doesn't exist
        #[arg(short, long)]
        create: bool,

        /// Skip repositories where the branch does not exist instead of failing
        #[arg(long)]
        ignore_missing: bool,
    },

    /// Pull latest changes in the active git scope
    Pull {
        /// Replay local commits on top of the incoming ones instead of
        /// refusing a branch that has diverged
        #[arg(short, long)]
        rebase: bool,

        /// Set aside local changes to the files the incoming commits rewrite,
        /// pull, then put them back on top
        #[arg(long)]
        autostash: bool,

        /// Throw away local changes to the files the incoming commits rewrite.
        /// Only those files; everything else in the tree is left alone
        #[arg(long, conflicts_with = "autostash")]
        overwrite_local: bool,
    },

    /// Push changes in the active git scope
    Push {
        /// Force push with lease
        #[arg(short, long)]
        force_with_lease: bool,

        /// Apply to every repository regardless of whether there are outgoing commits
        #[arg(long)]
        force_all: bool,
    },

    /// Create a new branch in the active git scope
    Create {
        /// Branch name to create
        branch: String,

        /// Base branch to create from
        #[arg(short, long)]
        from: Option<String>,

        /// Push the new branch immediately
        #[arg(short, long)]
        push: bool,

        /// Apply to every repository regardless of whether its worktree has changes
        #[arg(long)]
        force_all: bool,
    },

    /// Commit changes in the active git scope
    Commit {
        /// Commit message
        #[arg(short, long)]
        message: String,

        /// Push after committing
        #[arg(short, long)]
        push: bool,

        /// Stage all unstaged changes before committing (git add -A)
        #[arg(short = 'a', long)]
        stage_all: bool,

        /// Exclude repos by alias; may be repeated (applied after --include)
        #[arg(short = 'x', long = "exclude", value_name = "ALIAS")]
        exclude: Vec<String>,

        /// Include only these aliases (whitelist); may be repeated
        #[arg(short = 'i', long = "include", value_name = "ALIAS")]
        include: Vec<String>,

        /// Apply to every repository regardless of whether its worktree has changes
        #[arg(long)]
        force_all: bool,
    },

    /// Synchronize the active git scope
    Sync {
        /// Initialize missing submodules
        #[arg(short, long)]
        init: bool,

        /// Recursive sync for nested submodules
        #[arg(short, long)]
        recursive: bool,
    },

    /// Delete a branch from the active git scope
    Delete {
        /// Branch name to delete
        branch: String,

        /// Also delete from remote
        #[arg(short, long)]
        remote: bool,

        /// Force delete
        #[arg(short, long)]
        force: bool,
    },

    /// Fetch updates for the active git scope
    Fetch {
        /// Fetch from all remotes
        #[arg(short, long)]
        all: bool,

        /// Apply to every repository regardless of whether remote refs differ locally
        #[arg(long)]
        force_all: bool,
    },

    /// List branches in the active git scope
    Branch {
        /// Show all branches including remotes
        #[arg(short, long)]
        all: bool,
    },

    /// Initialize the active git scope
    Init {
        /// Recursively initialize nested submodules
        #[arg(short, long)]
        recursive: bool,
    },

    /// Generate per-file diffs for the active git scope
    Diff {
        /// Base ref to diff against (commit, branch, tag). Without this, diffs unstaged changes.
        #[arg(long, conflicts_with = "since")]
        base: Option<String>,

        /// Relative time period to diff since, e.g. 2d, 4h, 30m. Uses the newest commit older than the cutoff as base.
        #[arg(long, conflicts_with = "base")]
        since: Option<String>,

        /// Output directory for diff files
        #[arg(short, long, default_value = "diffs")]
        output: String,

        /// Include staged changes (only when no base ref is given)
        #[arg(short, long)]
        staged: bool,

        /// Remove the output directory before generating new diffs
        #[arg(long)]
        clean: bool,
    },

    /// Stage all changes in the active git scope
    Stage,

    /// Merge current branch back into the default branch for the active git scope
    MergeBack {
        /// Commit message for staging uncommitted changes before merge
        #[arg(short, long, default_value = "Pre-merge commit")]
        message: String,
    },

    /// Generate a test catalog for the active git scope
    TestCatalog {
        /// Output file path (default: TEST_CATALOG.md in repo root)
        #[arg(short, long)]
        output: Option<PathBuf>,
        /// Automatically stage the generated catalog file(s) with git add
        #[arg(long)]
        git_add: bool,
        /// Suppress progress output; only print on error
        #[arg(long, short)]
        quiet: bool,
    },
}