use std::process::{Command, Stdio};
fn retrying<T>(mut attempt: impl FnMut() -> std::io::Result<T>) -> std::io::Result<T> {
let mut delay = std::time::Duration::from_millis(10);
for tries_left in [2u8, 1, 0] {
match attempt() {
Err(e) if tries_left > 0 && transient(&e) => {
std::thread::sleep(delay);
delay *= 3;
}
other => return other,
}
}
unreachable!("the zero-tries arm returns")
}
fn transient(e: &std::io::Error) -> bool {
if matches!(
e.kind(),
std::io::ErrorKind::Interrupted | std::io::ErrorKind::WouldBlock
) {
return true;
}
matches!(e.raw_os_error(), Some(4 | 11 | 26 | 35))
}
pub fn stdout_in(dir: &std::path::Path, args: &[&str]) -> Option<String> {
let mut cmd = Command::new("git");
cmd.arg("-C").arg(dir).args(args).stderr(Stdio::null());
let out = retrying(|| cmd.output()).ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).trim().to_string())
}
pub fn succeeds_in(dir: &std::path::Path, args: &[&str]) -> bool {
let mut cmd = Command::new("git");
cmd.arg("-C")
.arg(dir)
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
retrying(|| cmd.status())
.map(|s| s.success())
.unwrap_or(false)
}
pub struct Output {
pub code: i32,
pub stdout: String,
pub stderr: String,
}
pub fn output(args: &[&str]) -> Option<Output> {
let mut cmd = Command::new("git");
cmd.args(args).stdin(Stdio::null());
let out = retrying(|| cmd.output()).ok()?;
Some(Output {
code: out.status.code()?,
stdout: String::from_utf8_lossy(&out.stdout).trim().to_string(),
stderr: String::from_utf8_lossy(&out.stderr).trim().to_string(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transient_covers_the_fork_pressure_kinds_and_nothing_else() {
for code in [4, 11, 26, 35] {
assert!(
transient(&std::io::Error::from_raw_os_error(code)),
"raw {code} is a loaded-machine hiccup"
);
}
assert!(transient(&std::io::Error::from(
std::io::ErrorKind::Interrupted
)));
assert!(!transient(&std::io::Error::from(
std::io::ErrorKind::NotFound
)));
assert!(!transient(&std::io::Error::from(
std::io::ErrorKind::PermissionDenied
)));
}
}