use std::io::ErrorKind;
use std::process::Output;
use tokio::process::Command;
use crate::errors::GitCallError;
pub async fn run(command: &'static str, git: &mut Command) -> Result<Output, GitCallError> {
outcome(command, git.output().await)
}
pub fn run_blocking(
command: &'static str,
git: &mut std::process::Command,
) -> Result<Output, GitCallError> {
outcome(command, git.output())
}
pub fn not_run(command: &'static str, source: std::io::Error) -> GitCallError {
if source.kind() == ErrorKind::InvalidInput {
GitCallError::NullByte { command }
} else {
GitCallError::NotStarted { command, source }
}
}
fn outcome(command: &'static str, output: std::io::Result<Output>) -> Result<Output, GitCallError> {
let output = output.map_err(|source| not_run(command, source))?;
if output.status.success() {
return Ok(output);
}
Err(GitCallError::Refused {
command,
status: output.status,
stderr: String::from_utf8_lossy(&output.stderr).trim().to_owned(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn a_git_that_cannot_start_is_told_apart_from_one_that_refused() {
let outcome = run("log", &mut Command::new("git-that-is-not-installed")).await;
assert!(matches!(
outcome,
Err(GitCallError::NotStarted { command: "log", source }) if source.kind() == std::io::ErrorKind::NotFound
));
}
#[tokio::test]
async fn a_null_byte_in_an_argument_or_the_directory_stops_the_call() {
let mut in_argument = Command::new("git");
in_argument.args(["log", "a\0b"]);
let mut in_directory = Command::new("git");
in_directory.arg("log").current_dir("a\0b");
for git in [&mut in_argument, &mut in_directory] {
assert!(matches!(
run("log", git).await,
Err(GitCallError::NullByte { command: "log" })
));
}
}
#[tokio::test]
async fn a_git_that_exits_with_failure_is_refused() {
let mut failing = Command::new("sh");
failing.args(["-c", "echo nope >&2; exit 3"]);
let outcome = run("config", &mut failing).await;
assert!(matches!(
outcome,
Err(GitCallError::Refused { command: "config", status, stderr })
if status.code() == Some(3) && stderr == "nope"
));
}
}