Skip to main content

git_cli/
completion.rs

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}