use std::io::Read;
use std::process::{Command, Output, Stdio};
use std::time::{Duration, Instant};
const POLL_INTERVAL: Duration = Duration::from_millis(50);
pub fn git_command() -> Command {
let mut cmd = Command::new("git");
cmd.env("GIT_TERMINAL_PROMPT", "0");
cmd.env("GIT_SSH_COMMAND", crate::config::ssh_command_val());
cmd.env("GIT_ALLOW_PROTOCOL", "https:ssh:git:file");
cmd.env("GIT_PROTOCOL_FROM_USER", "0");
cmd.env("GIT_ASKPASS", "");
cmd.env("SSH_ASKPASS", "");
cmd.env("GCM_INTERACTIVE", "Never");
cmd.stdin(Stdio::null());
cmd
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GitRunError {
Timeout(Duration),
Spawn(String),
}
impl std::fmt::Display for GitRunError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GitRunError::Timeout(d) => {
write!(f, "git did not respond within {}s and was cancelled", d.as_secs())
}
GitRunError::Spawn(e) => write!(f, "could not run git: {}", e),
}
}
}
impl std::error::Error for GitRunError {}
pub fn run_git_with_timeout(mut cmd: Command, timeout: Duration) -> Result<Output, GitRunError> {
if timeout.is_zero() {
return cmd.output().map_err(|e| GitRunError::Spawn(e.to_string()));
}
cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped());
let mut child = cmd.spawn().map_err(|e| GitRunError::Spawn(e.to_string()))?;
let mut out_pipe = child.stdout.take();
let mut err_pipe = child.stderr.take();
let out_reader = std::thread::spawn(move || {
let mut buf = Vec::new();
if let Some(p) = out_pipe.as_mut() {
let _ = p.read_to_end(&mut buf);
}
buf
});
let err_reader = std::thread::spawn(move || {
let mut buf = Vec::new();
if let Some(p) = err_pipe.as_mut() {
let _ = p.read_to_end(&mut buf);
}
buf
});
let started = Instant::now();
let status = loop {
match child.try_wait() {
Ok(Some(status)) => break Some(status),
Ok(None) => {
if started.elapsed() >= timeout {
let _ = child.kill();
let _ = child.wait();
break None;
}
std::thread::sleep(POLL_INTERVAL);
}
Err(e) => {
let _ = child.kill();
let _ = child.wait();
return Err(GitRunError::Spawn(e.to_string()));
}
}
};
let stdout = out_reader.join().unwrap_or_default();
let stderr = err_reader.join().unwrap_or_default();
match status {
Some(status) => Ok(Output { status, stdout, stderr }),
None => Err(GitRunError::Timeout(timeout)),
}
}