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 let output = command(repo, args).output().map_err(|source| spawn_error(args, source))?;
17 stdout(args, output)
18}
19
20pub fn git_text<T: AsRef<OsStr>>(repo: Option<&Path>, args: &[T]) -> Result<String, GitError> {
21 git(repo, args).map(|stdout| String::from_utf8_lossy(&stdout).trim().to_string())
22}
23
24pub fn git_with_stdin<T: AsRef<OsStr>>(repo: Option<&Path>, args: &[T], stdin: &[u8]) -> Result<Vec<u8>, GitError> {
25 let mut child = command(repo, args)
26 .stdin(Stdio::piped())
27 .stdout(Stdio::piped())
28 .stderr(Stdio::piped())
29 .spawn()
30 .map_err(|source| spawn_error(args, source))?;
31 child.stdin.take().expect("stdin is piped").write_all(stdin).map_err(|source| spawn_error(args, source))?;
32 let output = child.wait_with_output().map_err(|source| spawn_error(args, source))?;
33 stdout(args, output)
34}
35
36fn command<T: AsRef<OsStr>>(repo: Option<&Path>, args: &[T]) -> Command {
37 let mut command = Command::new("git");
38 if let Some(repo) = repo {
39 command.arg("-C").arg(repo);
40 }
41 command.args(args);
42 command
43}
44
45fn stdout<T: AsRef<OsStr>>(args: &[T], output: Output) -> Result<Vec<u8>, GitError> {
46 if output.status.success() {
47 Ok(output.stdout)
48 } else {
49 Err(GitError::Failed {
50 args: display(args),
51 stderr: String::from_utf8_lossy(&output.stderr).trim().to_string(),
52 })
53 }
54}
55
56fn spawn_error<T: AsRef<OsStr>>(args: &[T], source: std::io::Error) -> GitError {
57 GitError::Spawn { args: display(args), source }
58}
59
60fn display<T: AsRef<OsStr>>(args: &[T]) -> String {
61 args.iter().map(|arg| arg.as_ref().to_string_lossy()).collect::<Vec<_>>().join(" ")
62}