use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::Duration;
use super::git_proc::{collect, drain, quiet, wait};
const TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Debug, Clone)]
pub(super) struct Out {
pub code: Option<i32>,
pub stdout: String,
pub stderr: String,
}
impl Out {
pub(super) fn ok(&self) -> bool {
self.code == Some(0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum Unrunnable {
Missing,
TimedOut,
Io(String),
}
#[derive(Debug, Clone)]
pub(super) struct Git {
program: OsString,
dir: PathBuf,
timeout: Duration,
}
impl Git {
pub(super) fn new(program: impl Into<OsString>, dir: impl Into<PathBuf>) -> Self {
Self {
program: program.into(),
dir: dir.into(),
timeout: TIMEOUT,
}
}
#[cfg(all(test, unix))]
pub(super) fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub(super) fn dir(&self) -> &Path {
&self.dir
}
pub(super) fn at(&self, dir: PathBuf) -> Self {
Self {
dir,
..self.clone()
}
}
pub(super) fn run(&self, args: &[&str]) -> Result<Out, Unrunnable> {
let mut command = Command::new(&self.program);
command
.args(args)
.current_dir(&self.dir)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
quiet(&mut command);
let mut child = command.spawn().map_err(|e| match e.kind() {
std::io::ErrorKind::NotFound => Unrunnable::Missing,
_ => Unrunnable::Io(e.to_string()),
})?;
let stdout = drain(child.stdout.take());
let stderr = drain(child.stderr.take());
let status = wait(&mut child, self.timeout)?;
Ok(Out {
code: status.code(),
stdout: collect(stdout),
stderr: collect(stderr),
})
}
pub(super) fn text(&self, args: &[&str]) -> Option<String> {
let out = self.run(args).ok().filter(Out::ok)?;
Some(out.stdout.trim().to_string())
}
}