diff --git a/gix-command/src/lib.rs b/gix-command/src/lib.rs index 5f7710f6bb9..4ad7b5c2082 100644 --- a/gix-command/src/lib.rs +++ b/gix-command/src/lib.rs @@ -26,8 +26,10 @@ pub struct Prepare { pub args: Vec, /// environment variables to set in the spawned process. pub env: Vec<(OsString, OsString)>, - /// If `true`, we will use `sh` to execute the `command`. + /// If `true`, we will use `shell_program` or `sh` to execute the `command`. pub use_shell: bool, + /// The name or path to the shell program to use instead of `sh`. + pub shell_program: Option, /// If `true` (default `true` on windows and `false` everywhere else) /// we will see if it's safe to manually invoke `command` after splitting /// its arguments as a shell would do. @@ -103,6 +105,12 @@ mod prepare { self } + /// Set the name or path to the shell `program` to use, to avoid using the default shell which is `sh`. + pub fn with_shell_program(mut self, program: impl Into) -> Self { + self.shell_program = Some(program.into()); + self + } + /// Unconditionally turn off using the shell when spawning the command. /// Note that not using the shell is the default so an effective use of this method /// is some time after [`with_shell()`][Prepare::with_shell()] was called. @@ -199,7 +207,10 @@ mod prepare { cmd } None => { - let mut cmd = Command::new(if cfg!(windows) { "sh" } else { "/bin/sh" }); + let mut cmd = Command::new( + prep.shell_program + .unwrap_or(if cfg!(windows) { "sh" } else { "/bin/sh" }.into()), + ); cmd.arg("-c"); if !prep.args.is_empty() { if prep.command.to_str().map_or(true, |cmd| !cmd.contains("$@")) { @@ -417,6 +428,7 @@ pub mod shebang { pub fn prepare(cmd: impl Into) -> Prepare { Prepare { command: cmd.into(), + shell_program: None, context: None, stdin: std::process::Stdio::null(), stdout: std::process::Stdio::piped(), diff --git a/gix-command/tests/command.rs b/gix-command/tests/command.rs index 6ded03a4309..bb69876278c 100644 --- a/gix-command/tests/command.rs +++ b/gix-command/tests/command.rs @@ -279,6 +279,24 @@ mod prepare { ); } + #[test] + fn single_and_multiple_arguments_as_part_of_command_with_given_shell() { + let cmd = std::process::Command::from( + gix_command::prepare("ls first second third") + .with_shell() + .with_shell_program("/somepath/to/bash"), + ); + assert_eq!( + format!("{cmd:?}"), + if cfg!(windows) { + quoted(&["ls", "first", "second", "third"]) + } else { + quoted(&["/somepath/to/bash", "-c", "ls first second third", "--"]) + }, + "with shell, this works as it performs word splitting on Windows, but on linux (or without splitting) it uses the given shell" + ); + } + #[test] fn single_and_complex_arguments_as_part_of_command_with_shell() { let cmd = std::process::Command::from(gix_command::prepare("ls --foo \"a b\"").arg("additional").with_shell());