1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
use std::process::{Command, Stdio};
use bstr::{BString, ByteSlice, ByteVec};
use crate::{helper, Program};
/// The kind of helper program to use.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Kind {
/// The built-in `git credential` helper program, part of any `git` distribution.
Builtin,
/// A custom credentials helper, as identified just by the name with optional arguments
ExternalName {
/// The name like `foo` along with optional args, like `foo --arg --bar="a b"`, with arguments using `sh` shell quoting rules.
/// The program executed will be `git-credential-foo [args]` if `name_and_args` starts with `foo [args]`.
/// Note that a shell is only used if it's needed.
name_and_args: BString,
},
/// A custom credentials helper, as identified just by the absolute path to the program and optional arguments. The program is executed through a shell.
ExternalPath {
/// The absolute path to the executable, like `/path/to/exe` along with optional args, like `/path/to/exe --arg --bar="a b"`, with arguments using `sh`
/// shell quoting rules.
path_and_args: BString,
},
/// A script to execute with `sh`.
ExternalShellScript(BString),
}
/// Initialization
impl Program {
/// Create a new program of the given `kind`.
pub fn from_kind(kind: Kind) -> Self {
Program {
kind,
child: None,
stderr: true,
}
}
/// Parse the given input as per the custom helper definition, supporting `!<script>`, `name` and `/absolute/name`, the latter two
/// also support arguments which are ignored here.
pub fn from_custom_definition(input: impl Into<BString>) -> Self {
fn from_custom_definition_inner(mut input: BString) -> Program {
let kind = if input.starts_with(b"!") {
input.remove(0);
Kind::ExternalShellScript(input)
} else {
let path = gix_path::from_bstr(
input
.find_byte(b' ')
.map_or(input.as_slice(), |pos| &input[..pos])
.as_bstr(),
);
if gix_path::is_absolute(path) {
Kind::ExternalPath { path_and_args: input }
} else {
Kind::ExternalName { name_and_args: input }
}
};
Program {
kind,
child: None,
stderr: true,
}
}
from_custom_definition_inner(input.into())
}
/// Convert the program into the respective command, suitable to invoke `action`.
pub fn to_command(&self, action: &helper::Action) -> std::process::Command {
let git_program = gix_path::env::exe_invocation();
let mut cmd = match &self.kind {
Kind::Builtin => {
let mut cmd = Command::from(gix_command::prepare(git_program));
cmd.arg("credential").arg(action.as_arg(false));
cmd
}
Kind::ExternalName { name_and_args } => {
let mut args = name_and_args.clone();
args.insert_str(0, "credential-");
args.insert_str(0, " ");
args.insert_str(0, git_program.to_string_lossy().as_ref());
gix_command::prepare(gix_path::from_bstr(args.as_bstr()).into_owned())
.arg(action.as_arg(true))
.with_shell_allow_argument_splitting()
.into()
}
Kind::ExternalShellScript(for_shell)
| Kind::ExternalPath {
path_and_args: for_shell,
} => gix_command::prepare(gix_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() });
cmd
}
}
/// Builder
impl Program {
/// By default `stderr` of programs is inherited and typically displayed in the terminal.
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 = self.to_command(action);
gix_trace::debug!(cmd = ?cmd, "launching credential helper");
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()),
))
}
}
}
///
#[allow(clippy::empty_docs)]
pub mod main;
pub use main::function::main;