use std::process::{Command, Stdio};
pub fn stdout(args: &[&str]) -> Option<String> {
let out = Command::new("git")
.args(args)
.stderr(Stdio::null())
.output()
.ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).trim().to_string())
}
pub fn stdout_in(dir: &std::path::Path, args: &[&str]) -> Option<String> {
let out = Command::new("git")
.arg("-C")
.arg(dir)
.args(args)
.stderr(Stdio::null())
.output()
.ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).trim().to_string())
}
pub fn stdout_piped(args: &[&str], stdin: &str) -> Option<String> {
use std::io::Write;
let mut child = Command::new("git")
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.ok()?;
child.stdin.take()?.write_all(stdin.as_bytes()).ok()?;
let out = child.wait_with_output().ok()?;
out.status
.success()
.then(|| String::from_utf8_lossy(&out.stdout).into_owned())
}
pub fn stdout_piped_raw(args: &[&str], stdin: &str) -> Option<Vec<u8>> {
use std::io::Write;
let mut child = Command::new("git")
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.ok()?;
child.stdin.take()?.write_all(stdin.as_bytes()).ok()?;
let out = child.wait_with_output().ok()?;
out.status.success().then_some(out.stdout)
}
pub fn stdout_piped_in(dir: &std::path::Path, args: &[&str], stdin: &[u8]) -> Option<String> {
use std::io::Write;
let mut child = Command::new("git")
.arg("-C")
.arg(dir)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.ok()?;
child.stdin.take()?.write_all(stdin).ok()?;
let out = child.wait_with_output().ok()?;
out.status
.success()
.then(|| String::from_utf8_lossy(&out.stdout).trim().to_string())
}
pub fn stdout_raw(args: &[&str]) -> Option<Vec<u8>> {
let out = Command::new("git")
.args(args)
.stderr(Stdio::null())
.output()
.ok()?;
out.status.success().then_some(out.stdout)
}
pub struct Output {
pub code: i32,
pub stdout: String,
pub stderr: String,
}
pub fn output(args: &[&str]) -> Option<Output> {
let out = Command::new("git")
.args(args)
.stdin(Stdio::null())
.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(),
})
}
pub fn succeeds(args: &[&str]) -> bool {
Command::new("git")
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
pub fn stdout_paths(args: &[&str]) -> Option<Vec<String>> {
let (first, rest) = args.split_first()?;
let mut argv = Vec::with_capacity(args.len() + 1);
argv.push(*first);
argv.push("-z");
argv.extend_from_slice(rest);
stdout_raw(&argv).map(|raw| split_nul_paths(&raw))
}
pub(crate) fn split_nul_paths(raw: &[u8]) -> Vec<String> {
raw.split(|&b| b == 0)
.filter(|s| !s.is_empty())
.map(|s| String::from_utf8_lossy(s).into_owned())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn splits_on_nul_and_drops_the_trailing_empty_segment() {
assert_eq!(
split_nul_paths(b"src/main.rs\0Cargo.toml\0"),
vec!["src/main.rs", "Cargo.toml"]
);
}
#[test]
fn empty_input_is_no_paths() {
assert_eq!(split_nul_paths(b""), Vec::<String>::new());
}
#[test]
fn a_non_ascii_path_is_not_reinterpreted_as_its_quoted_form() {
let mut raw = "é.json".as_bytes().to_vec();
raw.push(0);
let got = split_nul_paths(&raw);
assert_eq!(got, vec!["é.json".to_string()]);
assert_ne!(got[0], "\"\\303\\251.json\"", "must not be the quoted form");
}
}