1use std::{io::ErrorKind, path::Path, process::Stdio};
2
3use anyhow::Context;
4
5pub(crate) enum GitError {
8 ProgramFailed(Vec<u8>),
9 ProgramNotFound,
10 Other(anyhow::Error),
11}
12
13impl std::fmt::Display for GitError {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 match self {
16 Self::ProgramNotFound => f.write_str("`git` command not found - is git installed?"),
17 Self::Other(e) => e.fmt(f),
18 Self::ProgramFailed(stderr) => match std::str::from_utf8(stderr) {
19 Ok(s) => f.write_str(s),
20 Err(_) => f.write_str("(cannot get error)"),
21 },
22 }
23 }
24}
25
26pub(crate) trait UnderstandGitResult {
27 fn understand_git_result(self) -> Result<Vec<u8>, GitError>;
28}
29
30impl UnderstandGitResult for Result<std::process::Output, std::io::Error> {
31 fn understand_git_result(self) -> Result<Vec<u8>, GitError> {
32 match self {
33 Ok(output) => {
34 if output.status.success() {
35 Ok(output.stdout)
36 } else {
37 Err(GitError::ProgramFailed(output.stderr))
38 }
39 }
40 Err(e) => match e.kind() {
41 ErrorKind::NotFound => Err(GitError::ProgramNotFound),
43 _ => {
44 let err = anyhow::Error::from(e).context("Failed to run `git` command");
45 Err(GitError::Other(err))
46 }
47 },
48 }
49 }
50}
51
52pub(crate) async fn is_in_git_repo(dir: &Path) -> anyhow::Result<bool> {
53 let mut cmd = tokio::process::Command::new("git");
54 cmd.arg("-C")
55 .arg(dir)
56 .arg("rev-parse")
57 .arg("--git-dir")
58 .stdout(Stdio::null())
59 .stderr(Stdio::null());
60
61 let status = cmd
62 .status()
63 .await
64 .context("checking if new app is in a git repo")?;
65 Ok(status.success())
66}
67
68pub(crate) async fn init_git_repo(dir: &Path) -> Result<(), GitError> {
69 let mut cmd = tokio::process::Command::new("git");
70 cmd.arg("-C").arg(dir).arg("init");
71
72 let result = cmd.output().await;
73 result.understand_git_result().map(|_| ())
74}