1
0
Fork 0
mirror of https://github.com/git/git.git synced 2024-05-23 23:06:12 +02:00
git/hook.c
Emily Shaffer 96e7225b31 hook: add 'run' subcommand
In order to enable hooks to be run as an external process, by a
standalone Git command, or by tools which wrap Git, provide an external
means to run all configured hook commands for a given hook event.

Most of our hooks require more complex functionality than this, but
let's start with the bare minimum required to support our simplest
hooks.

In terms of implementation the usage_with_options() and "goto usage"
pattern here mirrors that of
builtin/{commit-graph,multi-pack-index}.c.

Some of the implementation here, such as a function being named
run_hooks_opt() when it's tasked with running one hook, to using the
run_processes_parallel_tr2() API to run with jobs=1 is somewhere
between a bit odd and and an overkill for the current features of this
"hook run" command and the hook.[ch] API.

This code will eventually be able to run multiple hooks declared in
config in parallel, by starting out with these names and APIs we
reduce the later churn of renaming functions, switching from the
run_command() to run_processes_parallel_tr2() API etc.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Acked-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-01-07 15:19:34 -08:00

145 lines
3.0 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;
strvec_push(&cp->args, hook_path);
strvec_pushv(&cp->args, hook_cb->options->args.v);
/* Provide context for errors if necessary */
*pp_task_cb = (char *)hook_path;
/*
* 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;
const char *hook_path = pp_task_cp;
hook_cb->rc |= 1;
strbuf_addf(out, _("Couldn't start hook '%s'\n"),
hook_path);
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;
hook_cb->rc |= result;
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 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 (!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;
run_processes_parallel_tr2(jobs,
pick_next_hook,
notify_start_failure,
notify_hook_finished,
&cb_data,
"hook",
hook_name);
ret = cb_data.rc;
cleanup:
run_hooks_opt_clear(options);
return ret;
}