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(Arg::new("stdout").long("stdout").action(ArgAction::SetTrue))
62                .arg(
63                    Arg::new("print")
64                        .short('p')
65                        .long("print")
66                        .action(ArgAction::SetTrue),
67                )
68                .arg(Arg::new("both").long("both").action(ArgAction::SetTrue)),
69        )
70        .subcommand(
71            Command::new("root")
72                .about("Jump to git root")
73                .arg(Arg::new("shell").long("shell").action(ArgAction::SetTrue)),
74        )
75        .subcommand(
76            Command::new("commit-hash")
77                .visible_alias("hash")
78                .about("Resolve commit hash")
79                .arg(Arg::new("ref").value_name("ref")),
80        )
81        .subcommand(Command::new("help").about("Display help message for utils"))
82}
83
84fn build_reset_group() -> Command {
85    let count_arg = || Arg::new("count").value_name("count");
86
87    Command::new("reset")
88        .about("Reset helpers")
89        .subcommand(
90            Command::new("soft")
91                .about("Reset to HEAD~N (soft)")
92                .arg(count_arg()),
93        )
94        .subcommand(
95            Command::new("mixed")
96                .about("Reset to HEAD~N (mixed)")
97                .arg(count_arg()),
98        )
99        .subcommand(
100            Command::new("hard")
101                .about("Reset to HEAD~N (hard)")
102                .arg(count_arg()),
103        )
104        .subcommand(Command::new("undo").about("Undo last reset"))
105        .subcommand(Command::new("back-head").about("Checkout HEAD@{1}"))
106        .subcommand(Command::new("back-checkout").about("Return to previous branch"))
107        .subcommand(
108            Command::new("remote")
109                .about("Reset to remote branch")
110                .arg(Arg::new("ref").long("ref").value_name("ref"))
111                .arg(
112                    Arg::new("remote")
113                        .short('r')
114                        .long("remote")
115                        .value_name("remote"),
116                )
117                .arg(
118                    Arg::new("branch")
119                        .short('b')
120                        .long("branch")
121                        .value_name("branch"),
122                )
123                .arg(
124                    Arg::new("no-fetch")
125                        .long("no-fetch")
126                        .action(ArgAction::SetTrue),
127                )
128                .arg(Arg::new("prune").long("prune").action(ArgAction::SetTrue))
129                .arg(Arg::new("clean").long("clean").action(ArgAction::SetTrue))
130                .arg(
131                    Arg::new("set-upstream")
132                        .long("set-upstream")
133                        .action(ArgAction::SetTrue),
134                )
135                .arg(
136                    Arg::new("yes")
137                        .short('y')
138                        .long("yes")
139                        .action(ArgAction::SetTrue),
140                ),
141        )
142        .subcommand(Command::new("help").about("Display help message for reset"))
143}
144
145fn build_commit_group() -> Command {
146    Command::new("commit")
147        .about("Commit helpers")
148        .subcommand(
149            Command::new("context")
150                .about("Print commit context")
151                .arg(Arg::new("stdout").long("stdout").action(ArgAction::SetTrue))
152                .arg(Arg::new("both").long("both").action(ArgAction::SetTrue))
153                .arg(
154                    Arg::new("no-color")
155                        .long("no-color")
156                        .action(ArgAction::SetTrue),
157                )
158                .arg(
159                    Arg::new("include")
160                        .long("include")
161                        .value_name("glob")
162                        .num_args(1..),
163                ),
164        )
165        .subcommand(
166            Command::new("context-json")
167                .visible_aliases(["context_json", "contextjson", "json"])
168                .about("Print commit context as JSON")
169                .arg(Arg::new("stdout").long("stdout").action(ArgAction::SetTrue))
170                .arg(Arg::new("both").long("both").action(ArgAction::SetTrue))
171                .arg(Arg::new("pretty").long("pretty").action(ArgAction::SetTrue))
172                .arg(Arg::new("bundle").long("bundle").action(ArgAction::SetTrue))
173                .arg(Arg::new("out-dir").long("out-dir").value_name("path")),
174        )
175        .subcommand(
176            Command::new("to-stash")
177                .visible_alias("stash")
178                .about("Create stash from commit")
179                .arg(Arg::new("ref").value_name("ref")),
180        )
181        .subcommand(Command::new("help").about("Display help message for commit"))
182}
183
184fn build_branch_group() -> Command {
185    Command::new("branch")
186        .about("Branch helpers")
187        .subcommand(
188            Command::new("cleanup")
189                .visible_alias("delete-merged")
190                .about("Delete merged branches")
191                .arg(Arg::new("base").short('b').long("base").value_name("base"))
192                .arg(
193                    Arg::new("squash")
194                        .short('s')
195                        .long("squash")
196                        .action(ArgAction::SetTrue),
197                ),
198        )
199        .subcommand(Command::new("help").about("Display help message for branch"))
200}
201
202fn build_ci_group() -> Command {
203    Command::new("ci")
204        .about("CI helpers")
205        .subcommand(
206            Command::new("pick")
207                .about("Cherry-pick into CI branch")
208                .arg(
209                    Arg::new("remote")
210                        .short('r')
211                        .long("remote")
212                        .value_name("name"),
213                )
214                .arg(
215                    Arg::new("no-fetch")
216                        .long("no-fetch")
217                        .action(ArgAction::SetTrue),
218                )
219                .arg(
220                    Arg::new("force")
221                        .short('f')
222                        .long("force")
223                        .action(ArgAction::SetTrue),
224                )
225                .arg(Arg::new("stay").long("stay").action(ArgAction::SetTrue)),
226        )
227        .subcommand(Command::new("help").about("Display help message for ci"))
228}
229
230fn build_open_group() -> Command {
231    Command::new("open")
232        .about("Open remote pages")
233        .subcommand(
234            Command::new("repo")
235                .about("Open repository page")
236                .arg(remotes_arg()),
237        )
238        .subcommand(
239            Command::new("branch")
240                .about("Open branch tree page")
241                .arg(Arg::new("ref").value_name("ref")),
242        )
243        .subcommand(
244            Command::new("default-branch")
245                .visible_alias("default")
246                .about("Open default branch tree page")
247                .arg(remotes_arg()),
248        )
249        .subcommand(
250            Command::new("commit")
251                .about("Open commit page")
252                .arg(Arg::new("ref").value_name("ref")),
253        )
254        .subcommand(
255            Command::new("compare")
256                .about("Open compare page")
257                .arg(Arg::new("from").value_name("from"))
258                .arg(Arg::new("to").value_name("to")),
259        )
260        .subcommand(
261            Command::new("pr")
262                .visible_aliases(["pull-request", "mr", "merge-request"])
263                .about("Open pull or merge request page")
264                .arg(Arg::new("id").value_name("id")),
265        )
266        .subcommand(
267            Command::new("pulls")
268                .visible_aliases(["prs", "merge-requests", "mrs"])
269                .about("Open pull or merge request list"),
270        )
271        .subcommand(
272            Command::new("issues")
273                .visible_alias("issue")
274                .about("Open issues list/page")
275                .arg(Arg::new("id").value_name("id")),
276        )
277        .subcommand(
278            Command::new("actions")
279                .visible_alias("action")
280                .about("Open actions page")
281                .arg(Arg::new("workflow").value_name("workflow")),
282        )
283        .subcommand(
284            Command::new("releases")
285                .visible_alias("release")
286                .about("Open releases list/page")
287                .arg(Arg::new("tag").value_name("tag")),
288        )
289        .subcommand(
290            Command::new("tags")
291                .visible_alias("tag")
292                .about("Open tags list/page")
293                .arg(Arg::new("tag").value_name("tag")),
294        )
295        .subcommand(
296            Command::new("commits")
297                .visible_alias("history")
298                .about("Open commit history page")
299                .arg(Arg::new("ref").value_name("ref")),
300        )
301        .subcommand(
302            Command::new("file")
303                .visible_alias("blob")
304                .about("Open file page")
305                .arg(
306                    Arg::new("path")
307                        .value_name("path")
308                        .value_hint(ValueHint::FilePath),
309                )
310                .arg(Arg::new("ref").value_name("ref")),
311        )
312        .subcommand(
313            Command::new("blame")
314                .about("Open blame page")
315                .arg(
316                    Arg::new("path")
317                        .value_name("path")
318                        .value_hint(ValueHint::FilePath),
319                )
320                .arg(Arg::new("ref").value_name("ref")),
321        )
322        .subcommand(Command::new("help").about("Display help message for open"))
323}
324
325fn remotes_arg() -> Arg {
326    Arg::new("remote").value_name("remote")
327}