use std::ffi::{OsStr, OsString};
use std::io;
use std::io::Write;
use std::path::Path;
use std::process::{Command, ExitStatus, Stdio};
#[derive(Debug, Clone)]
pub struct Git {
globals: Vec<OsString>,
}
impl Git {
pub fn new(globals: Vec<OsString>) -> Self {
Git { globals }
}
fn command(&self, directory: Option<&Path>) -> Command {
let mut command = Command::new("git");
command.args(&self.globals);
if let Some(directory) = directory {
command.arg("-C").arg(directory);
}
command
}
pub fn capture<I, S>(&self, directory: Option<&Path>, args: I) -> io::Result<Vec<u8>>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let output = self
.command(directory)
.args(args)
.stdin(Stdio::null())
.stderr(Stdio::null())
.output()?;
if !output.status.success() {
return Err(io::Error::other("git exited with a failure status"));
}
Ok(output.stdout)
}
pub fn passthrough<I, S>(&self, directory: Option<&Path>, args: I) -> io::Result<ExitStatus>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
self.command(directory).args(args).status()
}
pub fn capture_with_input<I, S>(
&self,
directory: Option<&Path>,
args: I,
input: &[u8],
) -> io::Result<Vec<u8>>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let mut child = self
.command(directory)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()?;
let stdin = child.stdin.take();
let input = input.to_vec();
let writer = std::thread::spawn(move || {
if let Some(mut stdin) = stdin {
let _ = stdin.write_all(&input);
}
});
let output = child.wait_with_output()?;
let _ = writer.join();
if !output.status.success() {
return Err(io::Error::other("git exited with a failure status"));
}
Ok(output.stdout)
}
pub fn capture_line<I, S>(&self, directory: Option<&Path>, args: I) -> io::Result<String>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let bytes = self.capture(directory, args)?;
let text =
String::from_utf8(bytes).map_err(|_| io::Error::other("git printed non-UTF-8"))?;
Ok(text.trim_end_matches(['\n', '\r']).to_string())
}
pub fn config(&self, directory: &Path, key: &str) -> Option<String> {
self.capture_line(Some(directory), ["config", "--get", key])
.ok()
}
}