use std::process::{Command, Stdio};
use bstr::{BString, ByteSlice, ByteVec};
use crate::{helper, Program};
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Kind {
Builtin,
ExternalName {
name_and_args: BString,
},
ExternalPath {
path_and_args: BString,
},
ExternalShellScript(BString),
}
impl Program {
pub fn from_kind(kind: Kind) -> Self {
Program {
kind,
child: None,
stderr: true,
}
}
pub fn from_custom_definition(input: impl Into<BString>) -> Self {
let mut input = input.into();
let kind = if input.starts_with(b"!") {
input.remove(0);
Kind::ExternalShellScript(input)
} else {
let path = git_path::from_bstr(
input
.find_byte(b' ')
.map_or(input.as_slice(), |pos| &input[..pos])
.as_bstr(),
);
if git_path::is_absolute(path) {
Kind::ExternalPath { path_and_args: input }
} else {
input.insert_str(0, "git credential-");
Kind::ExternalName { name_and_args: input }
}
};
Program {
kind,
child: None,
stderr: true,
}
}
}
impl Program {
pub fn suppress_stderr(mut self) -> Self {
self.stderr = false;
self
}
}
impl Program {
pub(crate) fn start(
&mut self,
action: &helper::Action,
) -> std::io::Result<(std::process::ChildStdin, Option<std::process::ChildStdout>)> {
assert!(self.child.is_none(), "BUG: must not call `start()` twice");
let mut cmd = match &self.kind {
Kind::Builtin => {
let mut cmd = Command::new(cfg!(windows).then(|| "git.exe").unwrap_or("git"));
cmd.arg("credential").arg(action.as_arg(false));
cmd
}
Kind::ExternalShellScript(for_shell)
| Kind::ExternalName {
name_and_args: for_shell,
}
| Kind::ExternalPath {
path_and_args: for_shell,
} => git_command::prepare(git_path::from_bstr(for_shell.as_bstr()).as_ref())
.with_shell()
.arg(action.as_arg(true))
.into(),
};
cmd.stdin(Stdio::piped())
.stdout(if action.expects_output() {
Stdio::piped()
} else {
Stdio::null()
})
.stderr(if self.stderr { Stdio::inherit() } else { Stdio::null() });
let mut child = cmd.spawn()?;
let stdin = child.stdin.take().expect("stdin to be configured");
let stdout = child.stdout.take();
self.child = child.into();
Ok((stdin, stdout))
}
pub(crate) fn finish(&mut self) -> std::io::Result<()> {
let mut child = self.child.take().expect("Call `start()` before calling finish()");
let status = child.wait()?;
if status.success() {
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Credentials helper program failed with status code {:?}", status.code()),
))
}
}
}
pub mod main;
pub use main::function::main;