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 /// Seconds between merge-status polls
75 #[arg(long, default_value_t = 30)]
76 interval: u64,
77 },
78
79 /// Manage worktrees
80 Worktree {
81 #[command(subcommand)]
82 command: WorktreeCommands,
83 },
84}
85
86#[derive(Subcommand)]
87pub enum WorktreeCommands {
88 /// Manage a pre-warmed worktree pool
89 Pool {
90 #[command(subcommand)]
91 command: PoolCommands,
92 },
93}
94
95#[derive(Subcommand)]
96pub enum PoolCommands {
97 /// Pre-warm the pool with ready-to-use worktrees
98 Warm {
99 /// Target number of available worktrees in the pool
100 count: usize,
101 },
102
103 /// Acquire a worktree from the pool (prints path to stdout)
104 Acquire,
105
106 /// Show pool status
107 Status,
108
109 /// Release acquired worktree(s) back to the pool
110 Release {
111 /// Name of the worktree to release (defaults to all acquired)
112 name: Option<String>,
113 },
114
115 /// Remove all worktrees and clean up the pool
116 Drain {
117 /// Force drain even if worktrees are acquired
118 #[arg(long)]
119 force: bool,
120 },
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn await_requires_pr_number() {
129 // Without a PR number, parsing must fail.
130 assert!(Cli::try_parse_from(["gw", "await"]).is_err());
131 }
132
133 #[test]
134 fn await_defaults() {
135 let cli = Cli::try_parse_from(["gw", "await", "42"]).unwrap();
136 match cli.command {
137 Commands::Await {
138 pr,
139 open,
140 no_wait,
141 no_cleanup,
142 interval,
143 } => {
144 assert_eq!(pr, 42);
145 assert!(!open);
146 assert!(!no_wait);
147 assert!(!no_cleanup);
148 assert_eq!(interval, 30);
149 }
150 _ => panic!("expected Await command"),
151 }
152 }
153
154 #[test]
155 fn await_flags() {
156 let cli = Cli::try_parse_from([
157 "gw",
158 "await",
159 "42",
160 "--open",
161 "--no-wait",
162 "--no-cleanup",
163 "--interval",
164 "5",
165 ])
166 .unwrap();
167 match cli.command {
168 Commands::Await {
169 pr,
170 open,
171 no_wait,
172 no_cleanup,
173 interval,
174 } => {
175 assert_eq!(pr, 42);
176 assert!(open);
177 assert!(no_wait);
178 assert!(no_cleanup);
179 assert_eq!(interval, 5);
180 }
181 _ => panic!("expected Await command"),
182 }
183 }
184}