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 )
277 .subcommand(Command::new("help").about("Display help message for branch"))
278}
279
280fn build_ci_group() -> Command {
281 Command::new("ci")
282 .about("CI helpers")
283 .subcommand(
284 Command::new("pick")
285 .about("Cherry-pick into CI branch")
286 .arg(
287 Arg::new("remote")
288 .short('r')
289 .long("remote")
290 .help("Remote used for fetch/push")
291 .value_name("name"),
292 )
293 .arg(
294 Arg::new("no-fetch")
295 .long("no-fetch")
296 .help("Skip remote fetch before branch creation")
297 .action(ArgAction::SetTrue),
298 )
299 .arg(
300 Arg::new("force")
301 .short('f')
302 .long("force")
303 .help("Reset existing CI branch and force push")
304 .action(ArgAction::SetTrue),
305 )
306 .arg(
307 Arg::new("stay")
308 .long("stay")
309 .help("Stay on CI branch after push")
310 .action(ArgAction::SetTrue),
311 ),
312 )
313 .subcommand(Command::new("help").about("Display help message for ci"))
314}
315
316fn build_open_group() -> Command {
317 Command::new("open")
318 .about("Open remote pages")
319 .subcommand(
320 Command::new("repo")
321 .about("Open repository page")
322 .arg(remotes_arg()),
323 )
324 .subcommand(
325 Command::new("branch")
326 .about("Open branch tree page")
327 .arg(Arg::new("ref").value_name("ref")),
328 )
329 .subcommand(
330 Command::new("default-branch")
331 .visible_alias("default")
332 .about("Open default branch tree page")
333 .arg(remotes_arg()),
334 )
335 .subcommand(
336 Command::new("commit")
337 .about("Open commit page")
338 .arg(Arg::new("ref").value_name("ref")),
339 )
340 .subcommand(
341 Command::new("compare")
342 .about("Open compare page")
343 .arg(Arg::new("from").value_name("from"))
344 .arg(Arg::new("to").value_name("to")),
345 )
346 .subcommand(
347 Command::new("pr")
348 .visible_aliases(["pull-request", "mr", "merge-request"])
349 .about("Open pull or merge request page")
350 .arg(Arg::new("id").value_name("id")),
351 )
352 .subcommand(
353 Command::new("pulls")
354 .visible_aliases(["prs", "merge-requests", "mrs"])
355 .about("Open pull or merge request list"),
356 )
357 .subcommand(
358 Command::new("issues")
359 .visible_alias("issue")
360 .about("Open issues list/page")
361 .arg(Arg::new("id").value_name("id")),
362 )
363 .subcommand(
364 Command::new("actions")
365 .visible_alias("action")
366 .about("Open actions page")
367 .arg(Arg::new("workflow").value_name("workflow")),
368 )
369 .subcommand(
370 Command::new("releases")
371 .visible_alias("release")
372 .about("Open releases list/page")
373 .arg(Arg::new("tag").value_name("tag")),
374 )
375 .subcommand(
376 Command::new("tags")
377 .visible_alias("tag")
378 .about("Open tags list/page")
379 .arg(Arg::new("tag").value_name("tag")),
380 )
381 .subcommand(
382 Command::new("commits")
383 .visible_alias("history")
384 .about("Open commit history page")
385 .arg(Arg::new("ref").value_name("ref")),
386 )
387 .subcommand(
388 Command::new("file")
389 .visible_alias("blob")
390 .about("Open file page")
391 .arg(
392 Arg::new("path")
393 .value_name("path")
394 .value_hint(ValueHint::FilePath),
395 )
396 .arg(Arg::new("ref").value_name("ref")),
397 )
398 .subcommand(
399 Command::new("blame")
400 .about("Open blame page")
401 .arg(
402 Arg::new("path")
403 .value_name("path")
404 .value_hint(ValueHint::FilePath),
405 )
406 .arg(Arg::new("ref").value_name("ref")),
407 )
408 .subcommand(Command::new("help").about("Display help message for open"))
409}
410
411fn remotes_arg() -> Arg {
412 Arg::new("remote").value_name("remote")
413}