1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
use crate::prelude::*;
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "gitfleet")]
#[command(about = "Git operations over the active git scope", long_about = None)]
#[command(version)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
/// Enable verbose output
#[arg(short, long, global = true)]
pub verbose: bool,
/// Dry run mode (show what would happen without executing)
#[arg(short = 'n', long, global = true)]
pub dry_run: bool,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Show status of the active git scope
Status,
/// Find every worktree with local changes and, after confirmation, throw
/// those changes away
Discard {
/// Delete untracked files too. They are the ones git holds no copy of,
/// so this cannot be undone by anything
#[arg(long)]
untracked: bool,
/// Do not ask. For a script that has already decided
#[arg(long)]
yes: bool,
},
/// Checkout a branch in the active git scope
Checkout {
/// Branch name to checkout
branch: String,
/// Create the branch if it doesn't exist
#[arg(short, long)]
create: bool,
/// Skip repositories where the branch does not exist instead of failing
#[arg(long)]
ignore_missing: bool,
},
/// Pull latest changes in the active git scope
Pull {
/// Replay local commits on top of the incoming ones instead of
/// refusing a branch that has diverged
#[arg(short, long)]
rebase: bool,
/// Set aside local changes to the files the incoming commits rewrite,
/// pull, then put them back on top
#[arg(long)]
autostash: bool,
/// Throw away local changes to the files the incoming commits rewrite.
/// Only those files; everything else in the tree is left alone
#[arg(long, conflicts_with = "autostash")]
overwrite_local: bool,
},
/// Push changes in the active git scope
Push {
/// Force push with lease
#[arg(short, long)]
force_with_lease: bool,
/// Apply to every repository regardless of whether there are outgoing commits
#[arg(long)]
force_all: bool,
},
/// Create a new branch in the active git scope
Create {
/// Branch name to create
branch: String,
/// Base branch to create from
#[arg(short, long)]
from: Option<String>,
/// Push the new branch immediately
#[arg(short, long)]
push: bool,
/// Apply to every repository regardless of whether its worktree has changes
#[arg(long)]
force_all: bool,
},
/// Commit changes in the active git scope
Commit {
/// Commit message
#[arg(short, long)]
message: String,
/// Push after committing
#[arg(short, long)]
push: bool,
/// Stage all unstaged changes before committing (git add -A)
#[arg(short = 'a', long)]
stage_all: bool,
/// Exclude repos by alias; may be repeated (applied after --include)
#[arg(short = 'x', long = "exclude", value_name = "ALIAS")]
exclude: Vec<String>,
/// Include only these aliases (whitelist); may be repeated
#[arg(short = 'i', long = "include", value_name = "ALIAS")]
include: Vec<String>,
/// Apply to every repository regardless of whether its worktree has changes
#[arg(long)]
force_all: bool,
},
/// Synchronize the active git scope
Sync {
/// Initialize missing submodules
#[arg(short, long)]
init: bool,
/// Recursive sync for nested submodules
#[arg(short, long)]
recursive: bool,
},
/// Delete a branch from the active git scope
Delete {
/// Branch name to delete
branch: String,
/// Also delete from remote
#[arg(short, long)]
remote: bool,
/// Force delete
#[arg(short, long)]
force: bool,
},
/// Fetch updates for the active git scope
Fetch {
/// Fetch from all remotes
#[arg(short, long)]
all: bool,
/// Apply to every repository regardless of whether remote refs differ locally
#[arg(long)]
force_all: bool,
},
/// List branches in the active git scope
Branch {
/// Show all branches including remotes
#[arg(short, long)]
all: bool,
},
/// Initialize the active git scope
Init {
/// Recursively initialize nested submodules
#[arg(short, long)]
recursive: bool,
},
/// Generate per-file diffs for the active git scope
Diff {
/// Base ref to diff against (commit, branch, tag). Without this, diffs unstaged changes.
#[arg(long, conflicts_with = "since")]
base: Option<String>,
/// Relative time period to diff since, e.g. 2d, 4h, 30m. Uses the newest commit older than the cutoff as base.
#[arg(long, conflicts_with = "base")]
since: Option<String>,
/// Output directory for diff files
#[arg(short, long, default_value = "diffs")]
output: String,
/// Include staged changes (only when no base ref is given)
#[arg(short, long)]
staged: bool,
/// Remove the output directory before generating new diffs
#[arg(long)]
clean: bool,
},
/// Stage all changes in the active git scope
Stage,
/// Merge current branch back into the default branch for the active git scope
MergeBack {
/// Commit message for staging uncommitted changes before merge
#[arg(short, long, default_value = "Pre-merge commit")]
message: String,
},
/// Generate a test catalog for the active git scope
TestCatalog {
/// Output file path (default: TEST_CATALOG.md in repo root)
#[arg(short, long)]
output: Option<PathBuf>,
/// Automatically stage the generated catalog file(s) with git add
#[arg(long)]
git_add: bool,
/// Suppress progress output; only print on error
#[arg(long, short)]
quiet: bool,
},
}