use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
pub fn stdout_in(dir: &Path, args: &[&str]) -> Option<String> {
let out = Command::new("git")
.current_dir(dir)
.args(args)
.stdin(Stdio::null())
.stderr(Stdio::null())
.output()
.ok()?;
if !out.status.success() {
return None;
}
let text = String::from_utf8_lossy(&out.stdout).trim().to_string();
(!text.is_empty()).then_some(text)
}
pub fn succeeds_in(dir: &Path, args: &[&str]) -> bool {
Command::new("git")
.current_dir(dir)
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
pub fn stdout_bytes_in(dir: &Path, args: &[&str]) -> Option<Vec<u8>> {
let out = Command::new("git")
.current_dir(dir)
.args(args)
.stdin(Stdio::null())
.stderr(Stdio::null())
.output()
.ok()?;
out.status.success().then_some(out.stdout)
}
pub fn stdout_with_input_in(dir: &Path, args: &[&str], input: &[u8]) -> Option<String> {
let mut child = Command::new("git")
.current_dir(dir)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.ok()?;
{
let mut stdin = child.stdin.take()?;
stdin.write_all(input).ok()?;
}
let out = child.wait_with_output().ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).trim().to_string())
}
pub enum Remote {
Done(i32, String),
TimedOut,
Failed,
}
pub const REMOTE_BUDGET_SECS: u64 = 15;
pub fn remote(dir: &Path, args: &[&str]) -> Remote {
use std::time::{Duration, Instant};
let mut cmd = Command::new("git");
cmd.current_dir(dir)
.args([
"-c",
"http.lowSpeedLimit=1",
"-c",
"http.lowSpeedTime=10",
"-c",
"credential.interactive=never",
])
.args(args)
.env("GIT_TERMINAL_PROMPT", "0")
.env("GIT_ASKPASS", "")
.env("GCM_INTERACTIVE", "never")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null());
let set = |v: &str| std::env::var_os(v).is_some_and(|v| !v.is_empty());
let own_ssh = set("GIT_SSH_COMMAND")
|| set("GIT_SSH")
|| succeeds_in(dir, &["config", "--get", "core.sshCommand"]);
if !own_ssh {
cmd.env(
"GIT_SSH_COMMAND",
"ssh -o BatchMode=yes -o ConnectTimeout=10",
);
}
let Ok(mut child) = cmd.spawn() else {
return Remote::Failed;
};
let Some(mut out) = child.stdout.take() else {
return Remote::Failed;
};
let reader = std::thread::spawn(move || {
let mut buf = Vec::new();
let _ = std::io::Read::read_to_end(&mut out, &mut buf);
buf
});
let deadline = Instant::now() + Duration::from_secs(REMOTE_BUDGET_SECS);
let status = loop {
match child.try_wait() {
Ok(Some(status)) => break status,
Ok(None) if Instant::now() < deadline => std::thread::sleep(Duration::from_millis(50)),
Ok(None) => {
let _ = child.kill();
let _ = child.wait();
return Remote::TimedOut;
}
Err(_) => return Remote::Failed,
}
};
let Ok(stdout) = reader.join() else {
return Remote::Failed;
};
match status.code() {
Some(code) => Remote::Done(code, String::from_utf8_lossy(&stdout).to_string()),
None => Remote::Failed,
}
}