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}