git_workflow/cli.rs
1//! CLI argument parsing with clap
2
3use clap::{Parser, Subcommand};
4
5#[derive(Parser)]
6#[command(name = "gw")]
7#[command(about = "Git workflow CLI - type-safe worktree-aware git operations")]
8#[command(version)]
9pub struct Cli {
10 #[command(subcommand)]
11 pub command: Commands,
12
13 /// Show verbose output (git commands being run)
14 #[arg(short, long, global = true)]
15 pub verbose: bool,
16}
17
18#[derive(Subcommand)]
19pub enum Commands {
20 /// Switch to home branch and sync with origin/main
21 Home,
22
23 /// Create new branch from origin/main
24 New {
25 /// Name of the branch to create (e.g., feature/add-login)
26 branch: Option<String>,
27 },
28
29 /// Delete merged branch and return to home
30 Cleanup {
31 /// Branch to delete (defaults to current branch if not on home)
32 branch: Option<String>,
33 },
34
35 /// Show current repository state
36 Status,
37
38 /// Pause current work: WIP commit and return to home branch
39 Pause {
40 /// Optional message describing why work is paused
41 message: Option<String>,
42 },
43
44 /// Abandon current changes and return to home branch
45 Abandon,
46
47 /// Undo the last commit (soft reset HEAD~1)
48 Undo,
49
50 /// Sync current branch after base PR is merged (update base to main, rebase, force push)
51 Sync,
52
53 /// Open the PR for the current branch in the browser
54 Open,
55
56 /// Watch a specific PR until merged or closed, then clean up its branch
57 Await {
58 /// PR number to watch (required so the watcher stays bound to one PR
59 /// even if you switch branches, e.g. while working a stacked PR)
60 pr: u64,
61
62 /// Also open the PR in the browser before watching
63 #[arg(long)]
64 open: bool,
65
66 /// Skip waiting for CI checks
67 #[arg(long = "no-wait")]
68 no_wait: bool,
69
70 /// Do not clean up the branch after the PR is merged
71 #[arg(long = "no-cleanup")]
72 no_cleanup: bool,
73
74 /// Continue watching for merge even if CI checks fail
75 /// (default: stop and report the failure)
76 #[arg(long = "ignore-ci-failure")]
77 ignore_ci_failure: bool,
78
79 /// Seconds between merge-status polls
80 #[arg(long, default_value_t = 30)]
81 interval: u64,
82 },
83
84 /// Manage worktrees
85 Worktree {
86 #[command(subcommand)]
87 command: WorktreeCommands,
88 },
89}
90
91#[derive(Subcommand)]
92pub enum WorktreeCommands {
93 /// Manage a pre-warmed worktree pool
94 Pool {
95 #[command(subcommand)]
96 command: PoolCommands,
97 },
98}
99
100#[derive(Subcommand)]
101pub enum PoolCommands {
102 /// Pre-warm the pool with ready-to-use worktrees
103 Warm {
104 /// Target number of available worktrees in the pool
105 count: usize,
106 },
107
108 /// Acquire a worktree from the pool (prints path to stdout)
109 Acquire,
110
111 /// Show pool status
112 Status,
113
114 /// Release acquired worktree(s) back to the pool
115 Release {
116 /// Name of the worktree to release (defaults to all acquired)
117 name: Option<String>,
118 },
119
120 /// Remove all worktrees and clean up the pool
121 Drain {
122 /// Force drain even if worktrees are acquired
123 #[arg(long)]
124 force: bool,
125 },
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn await_requires_pr_number() {
134 // Without a PR number, parsing must fail.
135 assert!(Cli::try_parse_from(["gw", "await"]).is_err());
136 }
137
138 #[test]
139 fn await_defaults() {
140 let cli = Cli::try_parse_from(["gw", "await", "42"]).unwrap();
141 match cli.command {
142 Commands::Await {
143 pr,
144 open,
145 no_wait,
146 no_cleanup,
147 ignore_ci_failure,
148 interval,
149 } => {
150 assert_eq!(pr, 42);
151 assert!(!open);
152 assert!(!no_wait);
153 assert!(!no_cleanup);
154 assert!(!ignore_ci_failure);
155 assert_eq!(interval, 30);
156 }
157 _ => panic!("expected Await command"),
158 }
159 }
160
161 #[test]
162 fn await_flags() {
163 let cli = Cli::try_parse_from([
164 "gw",
165 "await",
166 "42",
167 "--open",
168 "--no-wait",
169 "--no-cleanup",
170 "--ignore-ci-failure",
171 "--interval",
172 "5",
173 ])
174 .unwrap();
175 match cli.command {
176 Commands::Await {
177 pr,
178 open,
179 no_wait,
180 no_cleanup,
181 ignore_ci_failure,
182 interval,
183 } => {
184 assert_eq!(pr, 42);
185 assert!(open);
186 assert!(no_wait);
187 assert!(no_cleanup);
188 assert!(ignore_ci_failure);
189 assert_eq!(interval, 5);
190 }
191 _ => panic!("expected Await command"),
192 }
193 }
194}