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    /// Generate shell completions for the given shell
24    #[arg(long, value_name = "SHELL")]
25    pub generate_completion: Option<String>,
26
27    #[command(subcommand)]
28    pub command: Option<Commands>,
29}
30
31#[derive(Subcommand, Debug)]
32pub enum Commands {
33    /// Create new worktree for feature branch
34    New {
35        /// Branch name for the new worktree
36        name: String,
37
38        /// Custom worktree path (default: ../<repo>-<branch>)
39        #[arg(short, long, value_hint = ValueHint::DirPath)]
40        path: Option<String>,
41
42        /// Base branch to create from (default: from config)
43        #[arg(short = 'b', long = "base")]
44        base: Option<String>,
45
46        /// Skip AI tool launch
47        #[arg(long = "no-term")]
48        no_term: bool,
49
50        /// Terminal launch method (e.g., tmux, iterm-tab, zellij)
51        #[arg(short = 'T', 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 = 'B', 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        /// Resolve target as worktree name (instead of branch)
81        #[arg(short, long)]
82        worktree: bool,
83
84        /// Resolve target as branch name (instead of worktree)
85        #[arg(short = 'b', long = "by-branch", conflicts_with = "worktree")]
86        by_branch: bool,
87    },
88
89    /// Merge feature branch into base branch
90    Merge {
91        /// Branch name (default: current worktree branch)
92        branch: Option<String>,
93
94        /// Interactive rebase
95        #[arg(short, long)]
96        interactive: bool,
97
98        /// Dry run (show what would happen)
99        #[arg(long)]
100        dry_run: bool,
101
102        /// Push to remote after merge
103        #[arg(long)]
104        push: bool,
105
106        /// Use AI to resolve merge conflicts
107        #[arg(long)]
108        ai_merge: bool,
109
110        /// Resolve target as worktree name (instead of branch)
111        #[arg(short, long)]
112        worktree: bool,
113    },
114
115    /// Resume AI work in a worktree
116    Resume {
117        /// Branch name to resume (default: current worktree)
118        branch: Option<String>,
119
120        /// Terminal launch method
121        #[arg(short = 'T', long)]
122        term: Option<String>,
123
124        /// Launch AI tool in background
125        #[arg(long)]
126        bg: bool,
127
128        /// Resolve target as worktree name (instead of branch)
129        #[arg(short, long)]
130        worktree: bool,
131
132        /// Resolve target as branch name (instead of worktree)
133        #[arg(short, long, conflicts_with = "worktree")]
134        by_branch: bool,
135    },
136
137    /// Open interactive shell or execute command in a worktree
138    Shell {
139        /// Worktree branch to shell into
140        worktree: Option<String>,
141
142        /// Command and arguments to execute
143        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
144        args: Vec<String>,
145    },
146
147    /// Show current worktree status
148    Status,
149
150    /// Delete a worktree
151    Delete {
152        /// Branch name or path of worktree to delete (default: current worktree)
153        target: Option<String>,
154
155        /// Keep the branch (only remove worktree)
156        #[arg(short = 'k', long)]
157        keep_branch: bool,
158
159        /// Also delete the remote branch
160        #[arg(short = 'r', long)]
161        delete_remote: bool,
162
163        /// Don't use --force flag
164        #[arg(long)]
165        no_force: bool,
166
167        /// Resolve target as worktree name (instead of branch)
168        #[arg(short, long)]
169        worktree: bool,
170
171        /// Resolve target as branch name (instead of worktree)
172        #[arg(short, long, conflicts_with = "worktree")]
173        branch: bool,
174    },
175
176    /// List all worktrees
177    #[command(alias = "ls")]
178    List,
179
180    /// Batch cleanup of worktrees
181    Clean {
182        /// Delete worktrees for branches already merged to base
183        #[arg(long)]
184        merged: bool,
185
186        /// Delete worktrees older than N days
187        #[arg(long, value_name = "DAYS")]
188        older_than: Option<u64>,
189
190        /// Interactive selection UI
191        #[arg(short, long)]
192        interactive: bool,
193
194        /// Show what would be deleted without deleting
195        #[arg(long)]
196        dry_run: bool,
197    },
198
199    /// Display worktree hierarchy as a tree
200    Tree,
201
202    /// Show worktree statistics
203    Stats,
204
205    /// Compare two branches
206    Diff {
207        /// First branch
208        branch1: String,
209        /// Second branch
210        branch2: String,
211        /// Show statistics only
212        #[arg(short, long)]
213        summary: bool,
214        /// Show changed files only
215        #[arg(short, long)]
216        files: bool,
217    },
218
219    /// Sync worktree with base branch
220    Sync {
221        /// Branch name (default: current worktree)
222        branch: Option<String>,
223
224        /// Sync all worktrees
225        #[arg(long)]
226        all: bool,
227
228        /// Only fetch updates without rebasing
229        #[arg(long)]
230        fetch_only: bool,
231
232        /// Use AI to resolve merge conflicts
233        #[arg(long)]
234        ai_merge: bool,
235
236        /// Resolve target as worktree name (instead of branch)
237        #[arg(short, long)]
238        worktree: bool,
239
240        /// Resolve target as branch name (instead of worktree)
241        #[arg(short, long, conflicts_with = "worktree")]
242        by_branch: bool,
243    },
244
245    /// Change base branch for a worktree
246    ChangeBase {
247        /// New base branch
248        new_base: String,
249        /// Branch name (default: current worktree)
250        branch: Option<String>,
251
252        /// Dry run (show what would happen)
253        #[arg(long)]
254        dry_run: bool,
255
256        /// Interactive rebase
257        #[arg(short, long)]
258        interactive: bool,
259
260        /// Resolve target as worktree name (instead of branch)
261        #[arg(short, long)]
262        worktree: bool,
263
264        /// Resolve target as branch name (instead of worktree)
265        #[arg(short, long, conflicts_with = "worktree")]
266        by_branch: bool,
267    },
268
269    /// Configuration management
270    Config {
271        #[command(subcommand)]
272        action: ConfigAction,
273    },
274
275    /// Backup and restore worktrees
276    Backup {
277        #[command(subcommand)]
278        action: BackupAction,
279    },
280
281    /// Stash management (worktree-aware)
282    Stash {
283        #[command(subcommand)]
284        action: StashAction,
285    },
286
287    /// Manage lifecycle hooks
288    Hook {
289        #[command(subcommand)]
290        action: HookAction,
291    },
292
293    /// Export worktree configuration to a file
294    Export {
295        /// Output file path
296        #[arg(short, long)]
297        output: Option<String>,
298    },
299
300    /// Import worktree configuration from a file
301    Import {
302        /// Path to the configuration file to import
303        import_file: String,
304
305        /// Apply the imported configuration (default: preview only)
306        #[arg(long)]
307        apply: bool,
308    },
309
310    /// Scan for repositories (global mode)
311    Scan {
312        /// Base directory to scan (default: home directory)
313        #[arg(short, long, value_hint = ValueHint::DirPath)]
314        dir: Option<std::path::PathBuf>,
315    },
316
317    /// Clean up stale registry entries (global mode)
318    Prune,
319
320    /// Run diagnostics
321    Doctor,
322
323    /// Check for updates / upgrade
324    Upgrade,
325
326    /// Interactive shell integration setup
327    ShellSetup,
328
329    /// [Internal] Get worktree path for a branch
330    #[command(name = "_path", hide = true)]
331    Path {
332        /// Branch name
333        branch: Option<String>,
334
335        /// List branch names (for tab completion)
336        #[arg(long)]
337        list_branches: bool,
338
339        /// Interactive worktree selection
340        #[arg(short, long)]
341        interactive: bool,
342    },
343
344    /// Generate shell function for gw-cd / cw-cd
345    #[command(name = "_shell-function", hide = true)]
346    ShellFunction {
347        /// Shell type: bash, zsh, fish, or powershell
348        shell: String,
349    },
350}
351
352#[derive(Subcommand, Debug)]
353pub enum ConfigAction {
354    /// Show current configuration
355    Show,
356    /// Set a configuration value
357    Set {
358        /// Dot-separated config key (e.g., git.default_base_branch)
359        key: String,
360        /// Value to set
361        value: String,
362    },
363    /// Use a predefined AI tool preset
364    UsePreset {
365        /// Preset name (e.g., claude, codex, no-op)
366        name: String,
367    },
368    /// List available presets
369    ListPresets,
370    /// Reset configuration to defaults
371    Reset,
372}
373
374#[derive(Subcommand, Debug)]
375pub enum BackupAction {
376    /// Create backup of worktree(s) using git bundle
377    Create {
378        /// Branch name to backup (default: current worktree)
379        branch: Option<String>,
380
381        /// Backup all worktrees
382        #[arg(long)]
383        all: bool,
384
385        /// Output directory for backups
386        #[arg(short, long)]
387        output: Option<String>,
388    },
389    /// List available backups
390    List {
391        /// Filter by branch name
392        branch: Option<String>,
393    },
394    /// Restore worktree from backup
395    Restore {
396        /// Branch name to restore
397        branch: String,
398
399        /// Custom path for restored worktree
400        #[arg(short, long)]
401        path: Option<String>,
402
403        /// Backup ID (timestamp) to restore (default: latest)
404        #[arg(long)]
405        id: Option<String>,
406    },
407}
408
409#[derive(Subcommand, Debug)]
410pub enum StashAction {
411    /// Save changes in current worktree to stash
412    Save {
413        /// Optional message to describe the stash
414        message: Option<String>,
415    },
416    /// List all stashes organized by worktree/branch
417    List,
418    /// Apply a stash to a different worktree
419    Apply {
420        /// Branch name of worktree to apply stash to
421        target_branch: String,
422
423        /// Stash reference (default: stash@{0})
424        #[arg(short, long, default_value = "stash@{0}")]
425        stash: String,
426    },
427}
428
429#[derive(Subcommand, Debug)]
430pub enum HookAction {
431    /// Add a new hook for an event
432    Add {
433        /// Hook event (e.g., worktree.post_create, merge.pre)
434        event: String,
435        /// Shell command to execute
436        command: String,
437        /// Custom hook identifier
438        #[arg(long)]
439        id: Option<String>,
440        /// Human-readable description
441        #[arg(short, long)]
442        description: Option<String>,
443    },
444    /// Remove a hook
445    Remove {
446        /// Hook event
447        event: String,
448        /// Hook identifier to remove
449        hook_id: String,
450    },
451    /// List all hooks
452    List {
453        /// Filter by event
454        event: Option<String>,
455    },
456    /// Enable a disabled hook
457    Enable {
458        /// Hook event
459        event: String,
460        /// Hook identifier
461        hook_id: String,
462    },
463    /// Disable a hook without removing it
464    Disable {
465        /// Hook event
466        event: String,
467        /// Hook identifier
468        hook_id: String,
469    },
470    /// Manually run all hooks for an event
471    Run {
472        /// Hook event to run
473        event: String,
474        /// Show what would be executed without running
475        #[arg(long)]
476        dry_run: bool,
477    },
478}