Files
git/hook.h
Adrian Ratiu 1566bc6806 hook: add hook.<event>.enabled switch
Add a hook.<event>.enabled config key that disables all hooks for
a given event, when set to false, acting as a high-level switch
above the existing per-hook hook.<friendly-name>.enabled.

Event-disabled hooks are shown in "git hook list" with an
"event-disabled" tab-separated prefix before the name:

$ git hook list test-hook
event-disabled  hook-1
event-disabled  hook-2

With --show-scope:

$ git hook list --show-scope test-hook
local   event-disabled  hook-1

When a hook is both per-hook disabled and event-disabled, only
"event-disabled" is shown: the event-level switch is the more
relevant piece of information, and the per-hook "disabled" status
will surface once the event is re-enabled.

Using an event name as a friendly-name (e.g. hook.<event>.enabled)
can cause ambiguity, so a fatal error is issued when using a known
event name and a warning is issued for unknown event name, since
a collision cannot be detected with certainty for unknown events.

Suggested-by: Patrick Steinhardt <ps@pks.im>
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-04-06 09:18:52 -07:00

284 lines
8.2 KiB
C

#ifndef HOOK_H
#define HOOK_H
#include "config.h"
#include "run-command.h"
#include "string-list.h"
#include "strmap.h"
#include "strvec.h"
struct repository;
typedef void (*hook_data_free_fn)(void *data);
typedef void *(*hook_data_alloc_fn)(void *init_ctx);
/**
* Represents a hook command to be run.
* Hooks can be:
* 1. "traditional" (found in the hooks directory)
* 2. "configured" (defined in Git's configuration via hook.<friendly-name>.event).
* The 'kind' field determines which part of the union 'u' is valid.
*/
struct hook {
enum {
HOOK_TRADITIONAL,
HOOK_CONFIGURED,
} kind;
union {
struct {
const char *path;
} traditional;
struct {
const char *friendly_name;
const char *command;
enum config_scope scope;
bool disabled;
bool event_disabled;
} configured;
} u;
/**
* Whether this hook may run in parallel with other hooks for the same
* event. Only useful for configured (named) hooks. Traditional hooks
* always default to 0 (serial). Set via `hook.<name>.parallel = true`.
*/
bool parallel;
/**
* 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;
/**
* Callback to free `feed_pipe_cb_data`.
*
* It is called automatically and points to the `feed_pipe_cb_data_free`
* provided via the `run_hook_opt` parameter.
*/
hook_data_free_fn data_free;
};
struct run_hooks_opt {
/* Environment vars to be set for each hook */
struct strvec env;
/* Args to be passed to each hook */
struct strvec args;
/* Emit an error if the hook is missing */
unsigned int error_if_missing:1;
/**
* Number of processes to parallelize across.
*
* If > 1, output will be buffered and de-interleaved (ungroup=0).
* If == 1, output will be real-time (ungroup=1).
* If == 0, the 'hook.jobs' config is used or, if the config is unset,
* defaults to 1 (serial execution).
*/
unsigned int jobs;
/**
* An optional initial working directory for the hook,
* translates to "struct child_process"'s "dir" member.
*/
const char *dir;
/**
* A pointer which if provided will be set to 1 or 0 depending
* on if a hook was started, regardless of whether or not that
* was successful. I.e. if the underlying start_command() was
* successful this will be set to 1.
*
* Used for avoiding TOCTOU races in code that would otherwise
* call hook_exist() after a "maybe hook run" to see if a hook
* was invoked.
*/
int *invoked_hook;
/**
* Send the hook's stdout to stderr.
*
* This is the default behavior for all hooks except pre-push,
* which keeps stdout and stderr separate for backwards compatibility.
* When parallel execution is requested (jobs > 1), get_hook_jobs()
* overrides this to 1 for all hooks so run-command can de-interleave
* their outputs correctly.
*/
unsigned int stdout_to_stderr:1;
/**
* Path to file which should be piped to stdin for each hook.
*/
const char *path_to_stdin;
/**
* Callback used to incrementally feed a child hook stdin pipe.
*
* Useful especially if a hook consumes large quantities of data
* (e.g. a list of all refs in a client push), so feeding it via
* in-memory strings or slurping to/from files is inefficient.
* While the callback allows piecemeal writing, it can also be
* used for smaller inputs, where it gets called only once.
*
* Add hook callback initalization context to `feed_pipe_ctx`.
* Add hook callback internal state to `feed_pipe_cb_data`.
*
*/
feed_pipe_fn feed_pipe;
/**
* Opaque data pointer used to pass context to `feed_pipe_fn`.
*
* It can be accessed via the second callback arg 'pp_cb':
* ((struct hook_cb_data *) pp_cb)->hook_cb->options->feed_pipe_ctx;
*
* The caller is responsible for managing the memory for this data.
* Only useful when using `run_hooks_opt.feed_pipe`, otherwise ignore it.
*/
void *feed_pipe_ctx;
/**
* Some hooks need to create a fresh `feed_pipe_cb_data` internal state,
* so they can keep track of progress without affecting one another.
*
* If provided, this function will be called to alloc & initialize the
* `feed_pipe_cb_data` for each hook.
*
* The `feed_pipe_ctx` pointer can be used to pass initialization data.
*/
hook_data_alloc_fn feed_pipe_cb_data_alloc;
/**
* Called to free the memory initialized by `feed_pipe_cb_data_alloc`.
*
* Must always be provided when `feed_pipe_cb_data_alloc` is provided.
*/
hook_data_free_fn feed_pipe_cb_data_free;
};
/**
* Default initializer for hooks. Parallelism is opt-in: .jobs = 0 defers to
* the 'hook.jobs' config, falling back to serial (1) if unset.
*/
#define RUN_HOOKS_OPT_INIT { \
.env = STRVEC_INIT, \
.args = STRVEC_INIT, \
.stdout_to_stderr = 1, \
.jobs = 0, \
}
/**
* Initializer for hooks that must always run sequentially regardless of
* 'hook.jobs'. Use this when git knows the hook cannot safely be parallelized
* .jobs = 1 is non-overridable.
*/
#define RUN_HOOKS_OPT_INIT_FORCE_SERIAL { \
.env = STRVEC_INIT, \
.args = STRVEC_INIT, \
.stdout_to_stderr = 1, \
.jobs = 1, \
}
struct hook_cb_data {
/* rc reflects the cumulative failure state */
int rc;
const char *hook_name;
/**
* 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;
};
/**
* 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().
*/
struct string_list *list_hooks(struct repository *r, const char *hookname,
struct run_hooks_opt *options);
/**
* Frees a struct hook stored as the util pointer of a string_list_item.
* Suitable for use as a string_list_clear_func_t callback.
*/
void hook_free(void *p, const char *str);
/**
* Frees the hook configuration cache stored in `struct repository`.
* Called by repo_clear().
*/
void hook_cache_clear(struct strmap *cache);
/**
* Returns true if `name` is a recognized hook event name
* (e.g. "pre-commit", "post-receive").
*/
bool is_known_hook(const char *name);
/**
* Returns the path to the hook file, or NULL if the hook is missing
* or disabled. Note that this points to static storage that will be
* overwritten by further calls to find_hook and run_hook_*.
*/
const char *find_hook(struct repository *r, const char *name);
/**
* A boolean version of find_hook()
*/
int hook_exists(struct repository *r, const char *hookname);
/**
* Takes a `hook_name`, resolves it to a path with find_hook(), and
* runs the hook for you with the options specified in "struct
* run_hooks opt". Will free memory associated with the "struct run_hooks_opt".
*
* Returns the status code of the run hook, or a negative value on
* error().
*/
int run_hooks_opt(struct repository *r, const char *hook_name,
struct run_hooks_opt *options);
/**
* A wrapper for run_hooks_opt() which provides a dummy "struct
* run_hooks_opt" initialized with "RUN_HOOKS_OPT_INIT".
*/
int run_hooks(struct repository *r, const char *hook_name);
/**
* Like run_hooks(), a wrapper for run_hooks_opt().
*
* In addition to the wrapping behavior provided by run_hooks(), this
* wrapper takes a list of strings terminated by a NULL
* argument. These things will be used as positional arguments to the
* hook. This function behaves like the old run_hook_le() API.
*/
LAST_ARG_MUST_BE_NULL
int run_hooks_l(struct repository *r, const char *hook_name, ...);
#endif