return path.buf;
}
+static void hook_clear(struct hook *h, cb_data_free_fn cb_data_free)
+{
+ if (!h)
+ return;
+
+ if (h->kind == HOOK_TRADITIONAL)
+ free((void *)h->u.traditional.path);
+
+ if (cb_data_free)
+ cb_data_free(h->feed_pipe_cb_data);
+
+ free(h);
+}
+
+static void hook_list_clear(struct string_list *hooks, cb_data_free_fn cb_data_free)
+{
+ struct string_list_item *item;
+
+ for_each_string_list_item(item, hooks)
+ hook_clear(item->util, cb_data_free);
+
+ string_list_clear(hooks, 0);
+}
+
+/* Helper to detect and add default "traditional" hooks from the hookdir. */
+static void list_hooks_add_default(struct repository *r, const char *hookname,
+ struct string_list *hook_list,
+ struct run_hooks_opt *options)
+{
+ const char *hook_path = find_hook(r, hookname);
+ struct hook *h;
+
+ if (!hook_path)
+ return;
+
+ h = xcalloc(1, sizeof(struct hook));
+
+ /*
+ * If the hook is to run in a specific dir, a relative path can
+ * become invalid in that dir, so convert to an absolute path.
+ */
+ if (options && options->dir)
+ hook_path = absolute_path(hook_path);
+
+ /* Setup per-hook internal state cb data */
+ if (options && options->feed_pipe_cb_data_alloc)
+ h->feed_pipe_cb_data = options->feed_pipe_cb_data_alloc(options->feed_pipe_ctx);
+
+ h->kind = HOOK_TRADITIONAL;
+ h->u.traditional.path = xstrdup(hook_path);
+
+ string_list_append(hook_list, hook_path)->util = h;
+}
+
+/*
+ * Provides a list of hook commands to run for the 'hookname' event.
+ *
+ * This function consolidates hooks from two sources:
+ * 1. The config-based hooks (not yet implemented).
+ * 2. The "traditional" hook found in the repository hooks directory
+ * (e.g., .git/hooks/pre-commit).
+ *
+ * The list is ordered by execution priority.
+ *
+ * The caller is responsible for freeing the memory of the returned list
+ * using string_list_clear() and free().
+ */
+static struct string_list *list_hooks(struct repository *r, const char *hookname,
+ struct run_hooks_opt *options)
+{
+ struct string_list *hook_head;
+
+ if (!hookname)
+ BUG("null hookname was provided to hook_list()!");
+
+ hook_head = xmalloc(sizeof(struct string_list));
+ string_list_init_dup(hook_head);
+
+ /* Add the default "traditional" hooks from hookdir. */
+ list_hooks_add_default(r, hookname, hook_head, options);
+
+ return hook_head;
+}
+
int hook_exists(struct repository *r, const char *name)
{
- return !!find_hook(r, name);
+ struct string_list *hooks = list_hooks(r, name, NULL);
+ int exists = hooks->nr > 0;
+ hook_list_clear(hooks, NULL);
+ free(hooks);
+ return exists;
}
static int pick_next_hook(struct child_process *cp,
void **pp_task_cb)
{
struct hook_cb_data *hook_cb = pp_cb;
- const char *hook_path = hook_cb->hook_path;
+ struct string_list *hook_list = hook_cb->hook_command_list;
+ struct hook *h;
- if (!hook_path)
+ if (hook_cb->hook_to_run_index >= hook_list->nr)
return 0;
+ h = hook_list->items[hook_cb->hook_to_run_index++].util;
+
cp->no_stdin = 1;
strvec_pushv(&cp->env, hook_cb->options->env.v);
cp->trace2_hook_name = hook_cb->hook_name;
cp->dir = hook_cb->options->dir;
- strvec_push(&cp->args, hook_path);
+ /* Add hook exec paths or commands */
+ if (h->kind == HOOK_TRADITIONAL)
+ strvec_push(&cp->args, h->u.traditional.path);
+
+ if (!cp->args.nr)
+ BUG("hook must have at least one command or exec path");
+
strvec_pushv(&cp->args, hook_cb->options->args.v);
/*
* Provide per-hook internal state via task_cb for easy access, so
* hook callbacks don't have to go through hook_cb->options.
*/
- *pp_task_cb = hook_cb->options->feed_pipe_cb_data;
-
- /*
- * 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;
+ *pp_task_cb = h->feed_pipe_cb_data;
return 1;
}
static void run_hooks_opt_clear(struct run_hooks_opt *options)
{
- if (options->feed_pipe_cb_data_free)
- options->feed_pipe_cb_data_free(options->feed_pipe_cb_data);
strvec_clear(&options->env);
strvec_clear(&options->args);
}
int run_hooks_opt(struct repository *r, 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(r, hook_name);
int ret = 0;
const struct run_process_parallel_opts opts = {
.tr2_category = "hook",
(!options->feed_pipe_cb_data_alloc && options->feed_pipe_cb_data_free))
BUG("feed_pipe_cb_data_alloc and feed_pipe_cb_data_free must be set together");
- if (options->feed_pipe_cb_data_alloc)
- options->feed_pipe_cb_data = options->feed_pipe_cb_data_alloc(options->feed_pipe_ctx);
-
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);
+ cb_data.hook_command_list = list_hooks(r, hook_name, options);
+ if (!cb_data.hook_command_list->nr) {
+ if (options->error_if_missing)
+ 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(&opts);
ret = cb_data.rc;
cleanup:
- strbuf_release(&abs_path);
+ hook_list_clear(cb_data.hook_command_list, options->feed_pipe_cb_data_free);
+ free(cb_data.hook_command_list);
run_hooks_opt_clear(options);
return ret;
}
#define HOOK_H
#include "strvec.h"
#include "run-command.h"
+#include "string-list.h"
struct repository;
+/**
+ * Represents a hook command to be run.
+ * Hooks can be:
+ * 1. "traditional" (found in the hooks directory)
+ * 2. "configured" (defined in Git's configuration, not yet implemented).
+ * The 'kind' field determines which part of the union 'u' is valid.
+ */
+struct hook {
+ enum {
+ HOOK_TRADITIONAL,
+ } kind;
+ union {
+ struct {
+ const char *path;
+ } traditional;
+ } u;
+
+ /**
+ * Opaque data pointer used to keep internal state across callback calls.
+ *
+ * It can be accessed directly via the third hook callback arg:
+ * struct ... *state = pp_task_cb;
+ *
+ * The caller is responsible for managing the memory for this data by
+ * providing alloc/free callbacks to `run_hooks_opt`.
+ *
+ * Only useful when using `run_hooks_opt.feed_pipe`, otherwise ignore it.
+ */
+ void *feed_pipe_cb_data;
+};
+
typedef void (*cb_data_free_fn)(void *data);
typedef void *(*cb_data_alloc_fn)(void *init_ctx);
*/
void *feed_pipe_ctx;
- /**
- * Opaque data pointer used to keep internal state across callback calls.
- *
- * It can be accessed directly via the third callback arg 'pp_task_cb':
- * struct ... *state = pp_task_cb;
- *
- * The caller is responsible for managing the memory for this data by
- * providing alloc/free callbacks to `run_hooks_opt`.
- *
- * Only useful when using `run_hooks_opt.feed_pipe`, otherwise ignore it.
- */
- void *feed_pipe_cb_data;
-
/**
* Some hooks need to create a fresh `feed_pipe_cb_data` internal state,
* so they can keep track of progress without affecting one another.
/* rc reflects the cumulative failure state */
int rc;
const char *hook_name;
- const char *hook_path;
+
+ /**
+ * A list of hook commands/paths to run for the 'hook_name' event.
+ *
+ * The 'string' member of each item holds the path (for traditional hooks)
+ * or the unique friendly-name for hooks specified in configs.
+ * The 'util' member of each item points to the corresponding struct hook.
+ */
+ struct string_list *hook_command_list;
+
+ /* Iterator/cursor for the above list, pointing to the next hook to run. */
+ size_t hook_to_run_index;
+
struct run_hooks_opt *options;
};