1use clap::{Arg, ArgAction, Command, ValueHint};
2use clap_complete::{Generator, Shell, generate};
3use std::io;
4
5pub fn dispatch(shell_raw: &str, extra: &[String]) -> i32 {
6 if !extra.is_empty() {
7 eprintln!("git-cli: error: expected `git-cli completion <bash|zsh>`");
8 return 1;
9 }
10
11 match shell_raw {
12 "bash" => generate_script(Shell::Bash),
13 "zsh" => generate_script(Shell::Zsh),
14 other => {
15 eprintln!("git-cli: error: unsupported completion shell '{other}'");
16 eprintln!("usage: git-cli completion <bash|zsh>");
17 1
18 }
19 }
20}
21
22fn generate_script<G: Generator>(generator: G) -> i32 {
23 let mut command = build_command_model();
24 let bin_name = command.get_name().to_string();
25 generate(generator, &mut command, bin_name, &mut io::stdout());
26 0
27}
28
29fn build_command_model() -> Command {
30 Command::new("git-cli")
31 .version(env!("CARGO_PKG_VERSION"))
32 .about("Git helper CLI")
33 .disable_help_subcommand(true)
34 .subcommand(build_utils_group())
35 .subcommand(build_reset_group())
36 .subcommand(build_commit_group())
37 .subcommand(build_branch_group())
38 .subcommand(build_ci_group())
39 .subcommand(build_open_group())
40 .subcommand(Command::new("help").about("Display help message for git-cli"))
41 .subcommand(
42 Command::new("completion")
43 .about("Export shell completion script")
44 .arg(
45 Arg::new("shell")
46 .value_name("shell")
47 .value_parser(["bash", "zsh"])
48 .required(true),
49 ),
50 )
51}
52
53fn build_utils_group() -> Command {
54 Command::new("utils")
55 .about("Utility helpers")
56 .subcommand(Command::new("zip").about("Create zip archive from HEAD"))
57 .subcommand(
58 Command::new("copy-staged")
59 .visible_alias("copy")
60 .about("Copy staged diff to clipboard")
61 .arg(
62 Arg::new("stdout")
63 .long("stdout")
64 .help("Print staged diff to stdout")
65 .action(ArgAction::SetTrue),
66 )
67 .arg(
68 Arg::new("print")
69 .short('p')
70 .long("print")
71 .help("Alias for --stdout")
72 .action(ArgAction::SetTrue),
73 )
74 .arg(
75 Arg::new("both")
76 .long("both")
77 .help("Print diff and copy it to clipboard")
78 .action(ArgAction::SetTrue),
79 ),
80 )
81 .subcommand(
82 Command::new("root").about("Jump to git root").arg(
83 Arg::new("shell")
84 .long("shell")
85 .help("Print shell command instead of plain output")
86 .action(ArgAction::SetTrue),
87 ),
88 )
89 .subcommand(
90 Command::new("commit-hash")
91 .visible_alias("hash")
92 .about("Resolve commit hash")
93 .arg(Arg::new("ref").value_name("ref")),
94 )
95 .subcommand(Command::new("help").about("Display help message for utils"))
96}
97
98fn build_reset_group() -> Command {
99 let count_arg = || Arg::new("count").value_name("count");
100
101 Command::new("reset")
102 .about("Reset helpers")
103 .subcommand(
104 Command::new("soft")
105 .about("Reset to HEAD~N (soft)")
106 .arg(count_arg()),
107 )
108 .subcommand(
109 Command::new("mixed")
110 .about("Reset to HEAD~N (mixed)")
111 .arg(count_arg()),
112 )
113 .subcommand(
114 Command::new("hard")
115 .about("Reset to HEAD~N (hard)")
116 .arg(count_arg()),
117 )
118 .subcommand(Command::new("undo").about("Undo last reset"))
119 .subcommand(Command::new("back-head").about("Checkout HEAD@{1}"))
120 .subcommand(Command::new("back-checkout").about("Return to previous branch"))
121 .subcommand(
122 Command::new("remote")
123 .about("Reset to remote branch")
124 .arg(
125 Arg::new("ref")
126 .long("ref")
127 .help("Remote ref in <remote>/<branch> form")
128 .value_name("ref"),
129 )
130 .arg(
131 Arg::new("remote")
132 .short('r')
133 .long("remote")
134 .help("Remote name")
135 .value_name("remote"),
136 )
137 .arg(
138 Arg::new("branch")
139 .short('b')
140 .long("branch")
141 .help("Remote branch name")
142 .value_name("branch"),
143 )
144 .arg(
145 Arg::new("no-fetch")
146 .long("no-fetch")
147 .help("Skip fetching remote refs")
148 .action(ArgAction::SetTrue),
149 )
150 .arg(
151 Arg::new("prune")
152 .long("prune")
153 .help("Run fetch with --prune")
154 .action(ArgAction::SetTrue),
155 )
156 .arg(
157 Arg::new("clean")
158 .long("clean")
159 .help("Run git clean -fd after reset")
160 .action(ArgAction::SetTrue),
161 )
162 .arg(
163 Arg::new("set-upstream")
164 .long("set-upstream")
165 .help("Set upstream to the target remote branch")
166 .action(ArgAction::SetTrue),
167 )
168 .arg(
169 Arg::new("yes")
170 .short('y')
171 .long("yes")
172 .help("Skip confirmation prompts")
173 .action(ArgAction::SetTrue),
174 ),
175 )
176 .subcommand(Command::new("help").about("Display help message for reset"))
177}
178
179fn build_commit_group() -> Command {
180 Command::new("commit")
181 .about("Commit helpers")
182 .subcommand(
183 Command::new("context")
184 .about("Print commit context")
185 .arg(
186 Arg::new("stdout")
187 .long("stdout")
188 .help("Print report to stdout")
189 .action(ArgAction::SetTrue),
190 )
191 .arg(
192 Arg::new("both")
193 .long("both")
194 .help("Print report and write output file")
195 .action(ArgAction::SetTrue),
196 )
197 .arg(
198 Arg::new("no-color")
199 .long("no-color")
200 .help("Disable ANSI colors")
201 .action(ArgAction::SetTrue),
202 )
203 .arg(
204 Arg::new("include")
205 .long("include")
206 .help("Additional glob(s) to include")
207 .value_name("glob")
208 .num_args(1..),
209 ),
210 )
211 .subcommand(
212 Command::new("context-json")
213 .visible_aliases(["context_json", "contextjson", "json"])
214 .about("Print commit context as JSON")
215 .arg(
216 Arg::new("stdout")
217 .long("stdout")
218 .help("Print JSON to stdout")
219 .action(ArgAction::SetTrue),
220 )
221 .arg(
222 Arg::new("both")
223 .long("both")
224 .help("Print JSON and write files")
225 .action(ArgAction::SetTrue),
226 )
227 .arg(
228 Arg::new("pretty")
229 .long("pretty")
230 .help("Pretty-print JSON output")
231 .action(ArgAction::SetTrue),
232 )
233 .arg(
234 Arg::new("bundle")
235 .long("bundle")
236 .help("Write bundle files to output directory")
237 .action(ArgAction::SetTrue),
238 )
239 .arg(
240 Arg::new("out-dir")
241 .long("out-dir")
242 .help("Output directory for generated files")
243 .value_name("path"),
244 ),
245 )
246 .subcommand(
247 Command::new("to-stash")
248 .visible_alias("stash")
249 .about("Create stash from commit")
250 .arg(Arg::new("ref").value_name("ref")),
251 )
252 .subcommand(Command::new("help").about("Display help message for commit"))
253}
254
255fn build_branch_group() -> Command {
256 Command::new("branch")
257 .about("Branch helpers")
258 .subcommand(
259 Command::new("cleanup")
260 .visible_alias("delete-merged")
261 .about("Delete merged branches")
262 .arg(
263 Arg::new("base")
264 .short('b')
265 .long("base")
266 .help("Base ref used to determine merged branches")
267 .value_name("base"),
268 )
269 .arg(
270 Arg::new("squash")
271 .short('s')
272 .long("squash")
273 .help("Include branches already applied via squash")
274 .action(ArgAction::SetTrue),
275 )
276 .arg(
277 Arg::new("remove-worktrees")
278 .short('w')
279 .long("remove-worktrees")
280 .help("Force-remove linked worktrees for candidate branches")
281 .action(ArgAction::SetTrue),
282 ),
283 )
284 .subcommand(Command::new("help").about("Display help message for branch"))
285}
286
287fn build_ci_group() -> Command {
288 Command::new("ci")
289 .about("CI helpers")
290 .subcommand(
291 Command::new("pick")
292 .about("Cherry-pick into CI branch")
293 .arg(
294 Arg::new("remote")
295 .short('r')
296 .long("remote")
297 .help("Remote used for fetch/push")
298 .value_name("name"),
299 )
300 .arg(
301 Arg::new("no-fetch")
302 .long("no-fetch")
303 .help("Skip remote fetch before branch creation")
304 .action(ArgAction::SetTrue),
305 )
306 .arg(
307 Arg::new("force")
308 .short('f')
309 .long("force")
310 .help("Reset existing CI branch and force push")
311 .action(ArgAction::SetTrue),
312 )
313 .arg(
314 Arg::new("stay")
315 .long("stay")
316 .help("Stay on CI branch after push")
317 .action(ArgAction::SetTrue),
318 ),
319 )
320 .subcommand(Command::new("help").about("Display help message for ci"))
321}
322
323fn build_open_group() -> Command {
324 Command::new("open")
325 .about("Open remote pages")
326 .subcommand(
327 Command::new("repo")
328 .about("Open repository page")
329 .arg(remotes_arg()),
330 )
331 .subcommand(
332 Command::new("branch")
333 .about("Open branch tree page")
334 .arg(Arg::new("ref").value_name("ref")),
335 )
336 .subcommand(
337 Command::new("default-branch")
338 .visible_alias("default")
339 .about("Open default branch tree page")
340 .arg(remotes_arg()),
341 )
342 .subcommand(
343 Command::new("commit")
344 .about("Open commit page")
345 .arg(Arg::new("ref").value_name("ref")),
346 )
347 .subcommand(
348 Command::new("compare")
349 .about("Open compare page")
350 .arg(Arg::new("from").value_name("from"))
351 .arg(Arg::new("to").value_name("to")),
352 )
353 .subcommand(
354 Command::new("pr")
355 .visible_aliases(["pull-request", "mr", "merge-request"])
356 .about("Open pull or merge request page")
357 .arg(Arg::new("id").value_name("id")),
358 )
359 .subcommand(
360 Command::new("pulls")
361 .visible_aliases(["prs", "merge-requests", "mrs"])
362 .about("Open pull or merge request list"),
363 )
364 .subcommand(
365 Command::new("issues")
366 .visible_alias("issue")
367 .about("Open issues list/page")
368 .arg(Arg::new("id").value_name("id")),
369 )
370 .subcommand(
371 Command::new("actions")
372 .visible_alias("action")
373 .about("Open actions page")
374 .arg(Arg::new("workflow").value_name("workflow")),
375 )
376 .subcommand(
377 Command::new("releases")
378 .visible_alias("release")
379 .about("Open releases list/page")
380 .arg(Arg::new("tag").value_name("tag")),
381 )
382 .subcommand(
383 Command::new("tags")
384 .visible_alias("tag")
385 .about("Open tags list/page")
386 .arg(Arg::new("tag").value_name("tag")),
387 )
388 .subcommand(
389 Command::new("commits")
390 .visible_alias("history")
391 .about("Open commit history page")
392 .arg(Arg::new("ref").value_name("ref")),
393 )
394 .subcommand(
395 Command::new("file")
396 .visible_alias("blob")
397 .about("Open file page")
398 .arg(
399 Arg::new("path")
400 .value_name("path")
401 .value_hint(ValueHint::FilePath),
402 )
403 .arg(Arg::new("ref").value_name("ref")),
404 )
405 .subcommand(
406 Command::new("blame")
407 .about("Open blame page")
408 .arg(
409 Arg::new("path")
410 .value_name("path")
411 .value_hint(ValueHint::FilePath),
412 )
413 .arg(Arg::new("ref").value_name("ref")),
414 )
415 .subcommand(Command::new("help").about("Display help message for open"))
416}
417
418fn remotes_arg() -> Arg {
419 Arg::new("remote").value_name("remote")
420}