spin_templates/
git.rs

1use std::{io::ErrorKind, path::Path, process::Stdio};
2
3use anyhow::Context;
4
5// TODO: the following and the second half of plugins/git.rs are duplicates
6
7pub(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                // TODO: consider cases like insufficient permission?
42                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}