148 lines · 5.4 KB · Rust
RawDownload
use std::process::{Command, Stdio}; use bstr::{BString, ByteSlice, ByteVec}; use crate::{Program, helper}; /// 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),} /// Initializationimpl 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))                    .command_may_be_shell_script_allow_manual_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())                .command_may_be_shell_script()                .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    }} /// Builderimpl 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::other(format!(                "Credentials helper program failed with status code {:?}",                status.code()            )))        }    }} ///pub mod main;pub use main::function::main;