Skip to main content

git_same/
cli.rs

1//! CLI argument parsing using clap.
2//!
3//! This module defines the command-line interface for git-same,
4//! including all subcommands and their options.
5
6use clap::{Args, Parser, Subcommand};
7use std::path::PathBuf;
8
9/// Git-Same - Mirror GitHub structure /orgs/repos/ to local file system
10///
11/// Available as: git-same (primary), gitsame, gitsa, gisa (symlink aliases)
12/// Alias list: see toolkit/packaging/binary-aliases.txt
13/// Also works as: git same (git subcommand)
14#[derive(Parser, Debug)]
15#[command(name = "git-same")]
16#[command(version, about, long_about = None)]
17#[command(propagate_version = true)]
18pub struct Cli {
19    /// Increase verbosity (-v, -vv, -vvv)
20    #[arg(short, long, action = clap::ArgAction::Count, global = true)]
21    pub verbose: u8,
22
23    /// Suppress all output except errors
24    #[arg(short, long, global = true)]
25    pub quiet: bool,
26
27    /// Output in JSON format
28    #[arg(long, global = true)]
29    pub json: bool,
30
31    /// Path to config file
32    #[arg(short = 'C', long, global = true)]
33    pub config: Option<PathBuf>,
34
35    #[command(subcommand)]
36    pub command: Option<Command>,
37}
38
39/// Git-Same subcommands
40#[derive(Subcommand, Debug)]
41pub enum Command {
42    /// Initialize git-same configuration
43    Init(InitArgs),
44
45    /// Configure a workspace (interactive wizard)
46    Setup(SetupArgs),
47
48    /// Sync repositories (discover, clone new, fetch/pull existing)
49    Sync(SyncCmdArgs),
50
51    /// Show status of local repositories
52    Status(StatusArgs),
53
54    /// Manage workspaces (list, set default)
55    Workspace(WorkspaceArgs),
56
57    /// Reset gisa — remove all config, workspaces, and cache
58    Reset(ResetArgs),
59
60    /// Scan a directory tree for unregistered workspaces (.git-same/ folders)
61    Scan(ScanArgs),
62}
63
64/// Arguments for the init command
65#[derive(Args, Debug)]
66pub struct InitArgs {
67    /// Force overwrite existing config
68    #[arg(short, long)]
69    pub force: bool,
70
71    /// Path for config file (default: ~/.config/git-same/config.toml)
72    #[arg(short, long)]
73    pub path: Option<PathBuf>,
74}
75
76/// Arguments for the setup command
77#[derive(Args, Debug)]
78pub struct SetupArgs {
79    /// Workspace name (auto-derived from base path if omitted)
80    #[arg(short, long)]
81    pub name: Option<String>,
82}
83
84/// Arguments for the sync command
85#[derive(Args, Debug)]
86pub struct SyncCmdArgs {
87    /// Workspace path or folder name to sync
88    #[arg(short, long)]
89    pub workspace: Option<String>,
90
91    /// Use pull instead of fetch for existing repos
92    #[arg(long)]
93    pub pull: bool,
94
95    /// Perform a dry run (show what would be done)
96    #[arg(short = 'n', long)]
97    pub dry_run: bool,
98
99    /// Maximum number of concurrent operations
100    #[arg(short, long)]
101    pub concurrency: Option<usize>,
102
103    /// Force re-discovery (ignore cache)
104    #[arg(long)]
105    pub refresh: bool,
106
107    /// Don't skip repositories with uncommitted changes
108    #[arg(long)]
109    pub no_skip_uncommitted: bool,
110}
111
112/// Arguments for the status command
113#[derive(Args, Debug)]
114pub struct StatusArgs {
115    /// Workspace path or folder name
116    #[arg(short, long)]
117    pub workspace: Option<String>,
118
119    /// Show only repositories with changes
120    #[arg(short = 'd', long)]
121    pub uncommitted: bool,
122
123    /// Show only repositories behind upstream
124    #[arg(short, long)]
125    pub behind: bool,
126
127    /// Show detailed status for each repository
128    #[arg(long)]
129    pub detailed: bool,
130
131    /// Filter to specific organizations (can be repeated)
132    #[arg(short, long)]
133    pub org: Vec<String>,
134}
135
136/// Arguments for the workspace command
137#[derive(Args, Debug)]
138pub struct WorkspaceArgs {
139    #[command(subcommand)]
140    pub command: WorkspaceCommand,
141}
142
143/// Workspace subcommands
144#[derive(Subcommand, Debug)]
145pub enum WorkspaceCommand {
146    /// List configured workspaces
147    List,
148    /// Set or show the default workspace
149    Default(WorkspaceDefaultArgs),
150}
151
152/// Arguments for the workspace default subcommand
153#[derive(Args, Debug)]
154pub struct WorkspaceDefaultArgs {
155    /// Workspace path or folder name to set as default (omit to show current)
156    #[arg(value_name = "WORKSPACE")]
157    pub name: Option<String>,
158
159    /// Clear the default workspace
160    #[arg(long)]
161    pub clear: bool,
162}
163
164/// Arguments for the reset command
165#[derive(Args, Debug)]
166pub struct ResetArgs {
167    /// Skip confirmation prompt
168    #[arg(short, long)]
169    pub force: bool,
170}
171
172/// Arguments for the scan command
173#[derive(Args, Debug)]
174pub struct ScanArgs {
175    /// Root directory to scan (default: current directory)
176    pub path: Option<PathBuf>,
177
178    /// Maximum directory depth to search (default: 5)
179    #[arg(short, long, default_value = "5")]
180    pub depth: usize,
181
182    /// Register discovered workspaces automatically
183    #[arg(long)]
184    pub register: bool,
185}
186
187impl Cli {
188    /// Parse command line arguments.
189    pub fn parse_args() -> Self {
190        Self::parse()
191    }
192
193    /// Get the effective verbosity level (0-3).
194    pub fn verbosity(&self) -> u8 {
195        if self.quiet {
196            0
197        } else {
198            self.verbose.min(3)
199        }
200    }
201
202    /// Check if output should be suppressed.
203    pub fn is_quiet(&self) -> bool {
204        self.quiet
205    }
206
207    /// Check if JSON output is requested.
208    pub fn is_json(&self) -> bool {
209        self.json
210    }
211}
212
213#[cfg(test)]
214#[path = "cli_tests.rs"]
215mod tests;