Skip to main content

utils/
git.rs

1use std::ffi::OsStr;
2use std::io::Write;
3use std::path::Path;
4use std::process::{Command, Output, Stdio};
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum GitError {
9    #[error("failed to spawn `git {args}`: {source}")]
10    Spawn { args: String, source: std::io::Error },
11    #[error("`git {args}` failed: {stderr}")]
12    Failed { args: String, stderr: String },
13}
14
15pub fn git<T: AsRef<OsStr>>(repo: Option<&Path>, args: &[T]) -> Result<Vec<u8>, GitError> {
16    git_with_env(repo, args, &[])
17}
18
19pub fn git_with_env<T: AsRef<OsStr>>(
20    repo: Option<&Path>,
21    args: &[T],
22    env: &[(&str, &OsStr)],
23) -> Result<Vec<u8>, GitError> {
24    let output = command(repo, args).envs(env.iter().copied()).output().map_err(|source| spawn_error(args, source))?;
25    stdout(args, output)
26}
27
28pub fn git_text<T: AsRef<OsStr>>(repo: Option<&Path>, args: &[T]) -> Result<String, GitError> {
29    git(repo, args).map(|stdout| String::from_utf8_lossy(&stdout).trim().to_string())
30}
31
32pub fn git_with_stdin<T: AsRef<OsStr>>(repo: Option<&Path>, args: &[T], stdin: &[u8]) -> Result<Vec<u8>, GitError> {
33    let mut child = command(repo, args)
34        .stdin(Stdio::piped())
35        .stdout(Stdio::piped())
36        .stderr(Stdio::piped())
37        .spawn()
38        .map_err(|source| spawn_error(args, source))?;
39    child.stdin.take().expect("stdin is piped").write_all(stdin).map_err(|source| spawn_error(args, source))?;
40    let output = child.wait_with_output().map_err(|source| spawn_error(args, source))?;
41    stdout(args, output)
42}
43
44fn command<T: AsRef<OsStr>>(repo: Option<&Path>, args: &[T]) -> Command {
45    let mut command = Command::new("git");
46    if let Some(repo) = repo {
47        command.arg("-C").arg(repo);
48    }
49    command.args(args);
50    command
51}
52
53fn stdout<T: AsRef<OsStr>>(args: &[T], output: Output) -> Result<Vec<u8>, GitError> {
54    if output.status.success() {
55        Ok(output.stdout)
56    } else {
57        Err(GitError::Failed {
58            args: display(args),
59            stderr: String::from_utf8_lossy(&output.stderr).trim().to_string(),
60        })
61    }
62}
63
64fn spawn_error<T: AsRef<OsStr>>(args: &[T], source: std::io::Error) -> GitError {
65    GitError::Spawn { args: display(args), source }
66}
67
68fn display<T: AsRef<OsStr>>(args: &[T]) -> String {
69    args.iter().map(|arg| arg.as_ref().to_string_lossy()).collect::<Vec<_>>().join(" ")
70}