mirror of
https://github.com/git/git.git
synced 2025-12-12 20:36:24 +01:00
Fix a logic error ina082345372(hook API: fix v2.36.0 regression: hooks should be connected to a TTY, 2022-06-07). When it started using the "ungroup" API added infd3aaf53f7(run-command: add an "ungroup" option to run_process_parallel(), 2022-06-07) it should have made the same sort of change thatfd3aaf53f7itself made in "t/helper/test-run-command.c". The correct way to emit this "Couldn't start" output with "ungroup" would be: fprintf(stderr, _("Couldn't start hook '%s'\n"), hook_path); But we should instead remove the emitting of this output. As the added test shows we already emit output when we can't run the child. The "cannot run" output here is emitted by run-command.c's child_err_spew(). So the addition of the "Couldn't start hook" output here in96e7225b31(hook: add 'run' subcommand, 2021-12-22) was always redundant. For the pre-commit hook we'll now emit exactly the same output as we did beforef443246b9f(commit: convert {pre-commit,prepare-commit-msg} hook to hook.h, 2021-12-22) (and likewise for others). We could at this point add this to the pick_next_hook() callbacks in hook.c: assert(!out); assert(!*pp_task_cb); And this to notify_start_failure() and notify_hook_finished() (in the latter case the parameter is called "pp_task_cp"): assert(!out); assert(!pp_task_cb); But let's leave any such instrumentation for some eventual cleanup of the "ungroup" API. Reported-by: Ilya K <me@0upti.me> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Reviewed-by: Emily Shaffer <emilyshaffer@google.com> Reviewed-by: Đoàn Trần Công Danh <congdanhqx@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
175 lines
3.6 KiB
C
175 lines
3.6 KiB
C
#include "cache.h"
|
|
#include "hook.h"
|
|
#include "run-command.h"
|
|
#include "config.h"
|
|
|
|
const char *find_hook(const char *name)
|
|
{
|
|
static struct strbuf path = STRBUF_INIT;
|
|
|
|
strbuf_reset(&path);
|
|
strbuf_git_path(&path, "hooks/%s", name);
|
|
if (access(path.buf, X_OK) < 0) {
|
|
int err = errno;
|
|
|
|
#ifdef STRIP_EXTENSION
|
|
strbuf_addstr(&path, STRIP_EXTENSION);
|
|
if (access(path.buf, X_OK) >= 0)
|
|
return path.buf;
|
|
if (errno == EACCES)
|
|
err = errno;
|
|
#endif
|
|
|
|
if (err == EACCES && advice_enabled(ADVICE_IGNORED_HOOK)) {
|
|
static struct string_list advise_given = STRING_LIST_INIT_DUP;
|
|
|
|
if (!string_list_lookup(&advise_given, name)) {
|
|
string_list_insert(&advise_given, name);
|
|
advise(_("The '%s' hook was ignored because "
|
|
"it's not set as executable.\n"
|
|
"You can disable this warning with "
|
|
"`git config advice.ignoredHook false`."),
|
|
path.buf);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
return path.buf;
|
|
}
|
|
|
|
int hook_exists(const char *name)
|
|
{
|
|
return !!find_hook(name);
|
|
}
|
|
|
|
static int pick_next_hook(struct child_process *cp,
|
|
struct strbuf *out,
|
|
void *pp_cb,
|
|
void **pp_task_cb)
|
|
{
|
|
struct hook_cb_data *hook_cb = pp_cb;
|
|
const char *hook_path = hook_cb->hook_path;
|
|
|
|
if (!hook_path)
|
|
return 0;
|
|
|
|
cp->no_stdin = 1;
|
|
strvec_pushv(&cp->env_array, hook_cb->options->env.v);
|
|
cp->stdout_to_stderr = 1;
|
|
cp->trace2_hook_name = hook_cb->hook_name;
|
|
cp->dir = hook_cb->options->dir;
|
|
|
|
strvec_push(&cp->args, hook_path);
|
|
strvec_pushv(&cp->args, hook_cb->options->args.v);
|
|
|
|
/*
|
|
* This pick_next_hook() will be called again, we're only
|
|
* running one hook, so indicate that no more work will be
|
|
* done.
|
|
*/
|
|
hook_cb->hook_path = NULL;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int notify_start_failure(struct strbuf *out,
|
|
void *pp_cb,
|
|
void *pp_task_cp)
|
|
{
|
|
struct hook_cb_data *hook_cb = pp_cb;
|
|
|
|
hook_cb->rc |= 1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int notify_hook_finished(int result,
|
|
struct strbuf *out,
|
|
void *pp_cb,
|
|
void *pp_task_cb)
|
|
{
|
|
struct hook_cb_data *hook_cb = pp_cb;
|
|
struct run_hooks_opt *opt = hook_cb->options;
|
|
|
|
hook_cb->rc |= result;
|
|
|
|
if (opt->invoked_hook)
|
|
*opt->invoked_hook = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void run_hooks_opt_clear(struct run_hooks_opt *options)
|
|
{
|
|
strvec_clear(&options->env);
|
|
strvec_clear(&options->args);
|
|
}
|
|
|
|
int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
|
|
{
|
|
struct strbuf abs_path = STRBUF_INIT;
|
|
struct hook_cb_data cb_data = {
|
|
.rc = 0,
|
|
.hook_name = hook_name,
|
|
.options = options,
|
|
};
|
|
const char *const hook_path = find_hook(hook_name);
|
|
int jobs = 1;
|
|
int ret = 0;
|
|
|
|
if (!options)
|
|
BUG("a struct run_hooks_opt must be provided to run_hooks");
|
|
|
|
if (options->invoked_hook)
|
|
*options->invoked_hook = 0;
|
|
|
|
if (!hook_path && !options->error_if_missing)
|
|
goto cleanup;
|
|
|
|
if (!hook_path) {
|
|
ret = error("cannot find a hook named %s", hook_name);
|
|
goto cleanup;
|
|
}
|
|
|
|
cb_data.hook_path = hook_path;
|
|
if (options->dir) {
|
|
strbuf_add_absolute_path(&abs_path, hook_path);
|
|
cb_data.hook_path = abs_path.buf;
|
|
}
|
|
|
|
run_processes_parallel_ungroup = 1;
|
|
run_processes_parallel_tr2(jobs,
|
|
pick_next_hook,
|
|
notify_start_failure,
|
|
notify_hook_finished,
|
|
&cb_data,
|
|
"hook",
|
|
hook_name);
|
|
ret = cb_data.rc;
|
|
cleanup:
|
|
strbuf_release(&abs_path);
|
|
run_hooks_opt_clear(options);
|
|
return ret;
|
|
}
|
|
|
|
int run_hooks(const char *hook_name)
|
|
{
|
|
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
|
|
|
|
return run_hooks_opt(hook_name, &opt);
|
|
}
|
|
|
|
int run_hooks_l(const char *hook_name, ...)
|
|
{
|
|
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
|
|
va_list ap;
|
|
const char *arg;
|
|
|
|
va_start(ap, hook_name);
|
|
while ((arg = va_arg(ap, const char *)))
|
|
strvec_push(&opt.args, arg);
|
|
va_end(ap);
|
|
|
|
return run_hooks_opt(hook_name, &opt);
|
|
}
|