Skip to main content

git_worktree_manager/
cli.rs

1/// CLI definitions using clap derive.
2///
3/// Mirrors the Typer-based CLI in src/git_worktree_manager/cli.py.
4pub mod completions;
5pub mod global;
6
7use clap::{Parser, Subcommand, ValueHint};
8
9/// Git worktree manager CLI.
10#[derive(Parser, Debug)]
11#[command(
12    name = "gw",
13    version,
14    about = "git worktree manager — AI coding assistant integration",
15    long_about = None,
16    arg_required_else_help = true,
17)]
18pub struct Cli {
19    /// Run in global mode (across all registered repositories)
20    #[arg(short = 'g', long = "global", global = true)]
21    pub global: bool,
22
23    #[command(subcommand)]
24    pub command: Option<Commands>,
25}
26
27#[derive(Subcommand, Debug)]
28pub enum Commands {
29    /// Create new worktree for feature branch
30    New {
31        /// Branch name for the new worktree
32        name: String,
33
34        /// Custom worktree path (default: ../<repo>-<branch>)
35        #[arg(short, long, value_hint = ValueHint::DirPath)]
36        path: Option<String>,
37
38        /// Base branch to create from (default: from config)
39        #[arg(short, long)]
40        branch: Option<String>,
41
42        /// Force creation even if branch exists
43        #[arg(short, long)]
44        force: bool,
45
46        /// Skip AI tool launch
47        #[arg(long)]
48        no_ai: bool,
49
50        /// Terminal launch method (e.g., tmux, iterm-tab, zellij)
51        #[arg(long)]
52        term: Option<String>,
53
54        /// Launch AI tool in background
55        #[arg(long)]
56        bg: bool,
57    },
58
59    /// Create GitHub Pull Request from worktree
60    Pr {
61        /// Branch name (default: current worktree branch)
62        branch: Option<String>,
63
64        /// PR title
65        #[arg(short, long)]
66        title: Option<String>,
67
68        /// PR body
69        #[arg(short, long)]
70        body: Option<String>,
71
72        /// Create as draft PR
73        #[arg(short, long)]
74        draft: bool,
75
76        /// Skip pushing to remote
77        #[arg(long)]
78        no_push: bool,
79    },
80
81    /// Merge feature branch into base branch
82    Merge {
83        /// Branch name (default: current worktree branch)
84        branch: Option<String>,
85
86        /// Interactive rebase
87        #[arg(short, long)]
88        interactive: bool,
89
90        /// Dry run (show what would happen)
91        #[arg(long)]
92        dry_run: bool,
93
94        /// Push to remote after merge
95        #[arg(long)]
96        push: bool,
97    },
98
99    /// Resume AI work in a worktree
100    Resume {
101        /// Branch name to resume (default: current worktree)
102        branch: Option<String>,
103
104        /// Terminal launch method
105        #[arg(long)]
106        term: Option<String>,
107
108        /// Launch AI tool in background
109        #[arg(long)]
110        bg: bool,
111    },
112
113    /// Open interactive shell or execute command in a worktree
114    Shell {
115        /// Worktree branch to shell into
116        worktree: Option<String>,
117
118        /// Command and arguments to execute
119        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
120        args: Vec<String>,
121    },
122
123    /// Show current worktree status
124    Status,
125
126    /// Delete a worktree
127    Delete {
128        /// Branch name or path of worktree to delete
129        target: String,
130
131        /// Keep the branch (only remove worktree)
132        #[arg(long)]
133        keep_branch: bool,
134
135        /// Also delete the remote branch
136        #[arg(long)]
137        delete_remote: bool,
138
139        /// Don't use --force flag
140        #[arg(long)]
141        no_force: bool,
142    },
143
144    /// List all worktrees
145    #[command(alias = "ls")]
146    List,
147
148    /// Batch cleanup of worktrees
149    Clean {
150        /// Delete worktrees for branches already merged to base
151        #[arg(long)]
152        merged: bool,
153
154        /// Delete worktrees older than N days
155        #[arg(long, value_name = "DAYS")]
156        older_than: Option<u64>,
157
158        /// Interactive selection UI
159        #[arg(short, long)]
160        interactive: bool,
161
162        /// Show what would be deleted without deleting
163        #[arg(long)]
164        dry_run: bool,
165    },
166
167    /// Display worktree hierarchy as a tree
168    Tree,
169
170    /// Show worktree statistics
171    Stats,
172
173    /// Compare two branches
174    Diff {
175        /// First branch
176        branch1: String,
177        /// Second branch
178        branch2: String,
179        /// Show statistics only
180        #[arg(long)]
181        summary: bool,
182        /// Show changed files only
183        #[arg(long)]
184        files: bool,
185    },
186
187    /// Sync worktree with base branch
188    Sync {
189        /// Branch name (default: current worktree)
190        branch: Option<String>,
191
192        /// Sync all worktrees
193        #[arg(long)]
194        all: bool,
195
196        /// Only fetch updates without rebasing
197        #[arg(long)]
198        fetch_only: bool,
199    },
200
201    /// Change base branch for a worktree
202    ChangeBase {
203        /// New base branch
204        new_base: String,
205        /// Branch name (default: current worktree)
206        branch: Option<String>,
207
208        /// Dry run (show what would happen)
209        #[arg(long)]
210        dry_run: bool,
211    },
212
213    /// Configuration management
214    Config {
215        #[command(subcommand)]
216        action: ConfigAction,
217    },
218
219    /// Backup and restore worktrees
220    Backup {
221        #[command(subcommand)]
222        action: BackupAction,
223    },
224
225    /// Stash management (worktree-aware)
226    Stash {
227        #[command(subcommand)]
228        action: StashAction,
229    },
230
231    /// Manage lifecycle hooks
232    Hook {
233        #[command(subcommand)]
234        action: HookAction,
235    },
236
237    /// Export worktree configuration to a file
238    Export {
239        /// Output file path
240        #[arg(short, long)]
241        output: Option<String>,
242    },
243
244    /// Import worktree configuration from a file
245    Import {
246        /// Path to the configuration file to import
247        import_file: String,
248
249        /// Apply the imported configuration (default: preview only)
250        #[arg(long)]
251        apply: bool,
252    },
253
254    /// Scan for repositories (global mode)
255    Scan,
256
257    /// Clean up stale registry entries (global mode)
258    Prune,
259
260    /// Run diagnostics
261    Doctor,
262
263    /// Check for updates / upgrade
264    Upgrade,
265
266    /// Interactive shell integration setup
267    ShellSetup,
268
269    /// [Internal] Get worktree path for a branch
270    #[command(name = "_path", hide = true)]
271    Path {
272        /// Branch name
273        branch: Option<String>,
274
275        /// List branch names (for tab completion)
276        #[arg(long)]
277        list_branches: bool,
278
279        /// Interactive worktree selection
280        #[arg(short, long)]
281        interactive: bool,
282    },
283
284    /// Generate shell function for cw-cd
285    #[command(name = "_shell-function", hide = true)]
286    ShellFunction {
287        /// Shell type: bash, zsh, or fish
288        shell: String,
289    },
290}
291
292#[derive(Subcommand, Debug)]
293pub enum ConfigAction {
294    /// Show current configuration
295    Show,
296    /// Set a configuration value
297    Set {
298        /// Dot-separated config key (e.g., git.default_base_branch)
299        key: String,
300        /// Value to set
301        value: String,
302    },
303    /// Use a predefined AI tool preset
304    UsePreset {
305        /// Preset name (e.g., claude, codex, no-op)
306        name: String,
307    },
308    /// List available presets
309    ListPresets,
310    /// Reset configuration to defaults
311    Reset,
312}
313
314#[derive(Subcommand, Debug)]
315pub enum BackupAction {
316    /// Create backup of worktree(s) using git bundle
317    Create {
318        /// Branch name to backup (default: current worktree)
319        branch: Option<String>,
320
321        /// Backup all worktrees
322        #[arg(long)]
323        all: bool,
324
325        /// Output directory for backups
326        #[arg(short, long)]
327        output: Option<String>,
328    },
329    /// List available backups
330    List {
331        /// Filter by branch name
332        branch: Option<String>,
333    },
334    /// Restore worktree from backup
335    Restore {
336        /// Branch name to restore
337        branch: String,
338
339        /// Custom path for restored worktree
340        #[arg(short, long)]
341        path: Option<String>,
342    },
343}
344
345#[derive(Subcommand, Debug)]
346pub enum StashAction {
347    /// Save changes in current worktree to stash
348    Save {
349        /// Optional message to describe the stash
350        message: Option<String>,
351    },
352    /// List all stashes organized by worktree/branch
353    List,
354    /// Apply a stash to a different worktree
355    Apply {
356        /// Branch name of worktree to apply stash to
357        target_branch: String,
358
359        /// Stash reference (default: stash@{0})
360        #[arg(short, long, default_value = "stash@{0}")]
361        stash: String,
362    },
363}
364
365#[derive(Subcommand, Debug)]
366pub enum HookAction {
367    /// Add a new hook for an event
368    Add {
369        /// Hook event (e.g., worktree.post_create, merge.pre)
370        event: String,
371        /// Shell command to execute
372        command: String,
373        /// Custom hook identifier
374        #[arg(long)]
375        id: Option<String>,
376        /// Human-readable description
377        #[arg(short, long)]
378        description: Option<String>,
379    },
380    /// Remove a hook
381    Remove {
382        /// Hook event
383        event: String,
384        /// Hook identifier to remove
385        hook_id: String,
386    },
387    /// List all hooks
388    List {
389        /// Filter by event
390        event: Option<String>,
391    },
392    /// Enable a disabled hook
393    Enable {
394        /// Hook event
395        event: String,
396        /// Hook identifier
397        hook_id: String,
398    },
399    /// Disable a hook without removing it
400    Disable {
401        /// Hook event
402        event: String,
403        /// Hook identifier
404        hook_id: String,
405    },
406    /// Manually run all hooks for an event
407    Run {
408        /// Hook event to run
409        event: String,
410        /// Show what would be executed without running
411        #[arg(long)]
412        dry_run: bool,
413    },
414}