diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt index c04f62a54a154c..2d3f790a90fb67 100644 --- a/Documentation/config/core.txt +++ b/Documentation/config/core.txt @@ -628,3 +628,7 @@ core.abbrev:: If set to "no", no abbreviation is made and the object names are shown in their full length. The minimum length is 4. + +core.shell:: + Set the absolute path to the executable to use as a POSIX shell; + This will also set/override the environment variable `SHELL`. diff --git a/compat/mingw.c b/compat/mingw.c index aa647b367b0fab..5d9cc59a707e0a 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1277,6 +1277,10 @@ static char *path_lookup(const char *cmd, int exe_only) if (strpbrk(cmd, "/\\")) return xstrdup(cmd); + if (!strcmp(cmd, "sh") && + (prog = xstrdup_or_null(get_shell_path(NULL)))) + return prog; + path = mingw_getenv("PATH"); if (!path) return NULL; @@ -1463,6 +1467,12 @@ static int is_msys2_sh(const char *cmd) if (ret >= 0) return ret; + if (get_shell_path(NULL)) { + /* Assume an overridden shell is not MSYS2 */ + ret = 0; + return ret; + } + p = path_lookup(cmd, 0); if (!p) ret = 0; diff --git a/help.c b/help.c index 3c3bdec21356d9..4690f7241df946 100644 --- a/help.c +++ b/help.c @@ -662,7 +662,7 @@ void get_version_info(struct strbuf *buf, int show_build_options) strbuf_addstr(buf, "no commit associated with this build\n"); strbuf_addf(buf, "sizeof-long: %d\n", (int)sizeof(long)); strbuf_addf(buf, "sizeof-size_t: %d\n", (int)sizeof(size_t)); - strbuf_addf(buf, "shell-path: %s\n", SHELL_PATH); + strbuf_addf(buf, "shell-path: %s\n", get_shell_path(SHELL_PATH)); /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */ } } diff --git a/run-command.c b/run-command.c index be6bc128cd9df2..38cb5b112cb58c 100644 --- a/run-command.c +++ b/run-command.c @@ -9,6 +9,20 @@ #include "quote.h" #include "config.h" +const char *get_shell_path(const char *fallback) +{ + static const char *shell; + static int initialized; + + if (!initialized) { + if (!git_config_get_pathname("core.shell", &shell)) + setenv("SHELL", shell, 1); + initialized = 1; + } + + return shell ? shell : fallback; +} + void child_process_init(struct child_process *child) { memset(child, 0, sizeof(*child)); @@ -271,9 +285,9 @@ static const char **prepare_shell_cmd(struct strvec *out, const char **argv) if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) { #ifndef GIT_WINDOWS_NATIVE - strvec_push(out, SHELL_PATH); + strvec_push(out, get_shell_path(SHELL_PATH)); #else - strvec_push(out, "sh"); + strvec_push(out, get_shell_path("sh")); #endif strvec_push(out, "-c"); @@ -411,7 +425,7 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd) * Add SHELL_PATH so in the event exec fails with ENOEXEC we can * attempt to interpret the command with 'sh'. */ - strvec_push(out, SHELL_PATH); + strvec_push(out, get_shell_path(SHELL_PATH)); if (cmd->git_cmd) { prepare_git_cmd(out, cmd->argv); diff --git a/run-command.h b/run-command.h index d08414a92e734c..4f10b63184dfad 100644 --- a/run-command.h +++ b/run-command.h @@ -483,4 +483,9 @@ int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn, task_finished_fn, void *pp_cb, const char *tr2_category, const char *tr2_label); +/* + * Get the path of the POSIX shell to use in `start_command()`. + */ +const char *get_shell_path(const char *fallback); + #endif diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index 7d599675e35a75..8f7618476db183 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh @@ -233,4 +233,18 @@ test_expect_success MINGW 'can spawn .bat with argv[0] containing spaces' ' grep "git-upload-pack" out ' +SQ="'" +test_expect_success 'core.shell' ' + test_config_global core.shell "$GIT_EXEC_PATH/git$X" && + test_must_fail git -c alias.eq="!a.b=c" eq 2>actual && + grep "${SQ}a.b=c${SQ} is not a git command" actual +' + +test_expect_success MINGW 'core.shell executes scripts' ' + test_config_global core.shell "$GIT_EXEC_PATH/git$X" && + echo "#!/bin/sh" >script && + test_must_fail git -c alias.s="!./script" s 2>actual && + grep "${SQ}./script${SQ} is not a git command" actual +' + test_done