--------
[verse]
'git hook' run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]
-'git hook' list [-z] <hook-name>
+'git hook' list [-z] [--show-scope] <hook-name>
DESCRIPTION
-----------
mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
linkgit:githooks[5] for arguments hooks might expect (if any).
-list [-z]::
+list [-z] [--show-scope]::
Print a list of hooks which will be run on `<hook-name>` event. If no
hooks are configured for that event, print a warning and return 1.
Use `-z` to terminate output lines with NUL instead of newlines.
-z::
Terminate "list" output lines with NUL instead of newlines.
+--show-scope::
+ For "list"; prefix each configured hook's friendly name with a
+ tab-separated config scope (e.g. `local`, `global`, `system`),
+ mirroring the output style of `git config --show-scope`. Traditional
+ hooks from the hookdir are unaffected.
+
WRAPPERS
--------
#define BUILTIN_HOOK_RUN_USAGE \
N_("git hook run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]")
#define BUILTIN_HOOK_LIST_USAGE \
- N_("git hook list [-z] <hook-name>")
+ N_("git hook list [-z] [--show-scope] <hook-name>")
static const char * const builtin_hook_usage[] = {
BUILTIN_HOOK_RUN_USAGE,
struct string_list_item *item;
const char *hookname = NULL;
int line_terminator = '\n';
+ int show_scope = 0;
int ret = 0;
struct option list_options[] = {
OPT_SET_INT('z', NULL, &line_terminator,
N_("use NUL as line terminator"), '\0'),
+ OPT_BOOL(0, "show-scope", &show_scope,
+ N_("show the config scope that defined each hook")),
OPT_END(),
};
printf("%s%c", _("hook from hookdir"), line_terminator);
break;
case HOOK_CONFIGURED:
- printf("%s%c", h->u.configured.friendly_name, line_terminator);
+ if (show_scope)
+ printf("%s\t%s%c",
+ config_scope_name(h->u.configured.scope),
+ h->u.configured.friendly_name,
+ line_terminator);
+ else
+ printf("%s%c", h->u.configured.friendly_name,
+ line_terminator);
break;
default:
BUG("unknown hook kind");
/*
* Cache entry stored as the .util pointer of string_list items inside the
- * hook config cache. For now carries only the command for the hook. Next
- * commits will add more data.
+ * hook config cache.
*/
struct hook_config_cache_entry {
char *command;
+ enum config_scope scope;
};
/*
/* repo_config() callback that collects all hook.* configuration in one pass. */
static int hook_config_lookup_all(const char *key, const char *value,
- const struct config_context *ctx UNUSED,
+ const struct config_context *ctx,
void *cb_data)
{
struct hook_all_config_cb *data = cb_data;
/* Re-insert if necessary to preserve last-seen order. */
unsorted_string_list_remove(hooks, hook_name, 0);
- string_list_append(hooks, hook_name);
+
+ if (!ctx->kvi)
+ BUG("hook config callback called without key-value info");
+
+ /*
+ * Stash the config scope in the util pointer for
+ * later retrieval in build_hook_config_map(). This
+ * intermediate struct is transient and never leaves
+ * that function, so we pack the enum value into the
+ * pointer rather than heap-allocating a wrapper.
+ */
+ string_list_append(hooks, hook_name)->util =
+ (void *)(uintptr_t)ctx->kvi->scope;
}
} else if (!strcmp(subkey, "command")) {
/* Store command overwriting the old value */
for (size_t i = 0; i < hook_names->nr; i++) {
const char *hname = hook_names->items[i].string;
+ enum config_scope scope =
+ (enum config_scope)(uintptr_t)hook_names->items[i].util;
struct hook_config_cache_entry *entry;
char *command;
/* util stores a cache entry; owned by the cache. */
CALLOC_ARRAY(entry, 1);
entry->command = xstrdup(command);
+ entry->scope = scope;
string_list_append(hooks, hname)->util = entry;
}
hook->kind = HOOK_CONFIGURED;
hook->u.configured.friendly_name = xstrdup(friendly_name);
hook->u.configured.command = xstrdup(entry->command);
+ hook->u.configured.scope = entry->scope;
string_list_append(list, friendly_name)->util = hook;
}
#ifndef HOOK_H
#define HOOK_H
+#include "config.h"
#include "run-command.h"
#include "string-list.h"
#include "strmap.h"
struct {
const char *friendly_name;
const char *command;
+ enum config_scope scope;
} configured;
} u;
test_cmp expected actual
'
+test_expect_success 'git hook list --show-scope shows config scope' '
+ setup_hookdir &&
+ test_config_global hook.global-hook.command "echo global" &&
+ test_config_global hook.global-hook.event pre-commit --add &&
+ test_config hook.local-hook.command "echo local" &&
+ test_config hook.local-hook.event pre-commit --add &&
+
+ cat >expected <<-\EOF &&
+ global global-hook
+ local local-hook
+ hook from hookdir
+ EOF
+ git hook list --show-scope pre-commit >actual &&
+ test_cmp expected actual &&
+
+ # without --show-scope the scope must not appear
+ git hook list pre-commit >actual &&
+ test_grep ! "^global " actual &&
+ test_grep ! "^local " actual
+'
+
test_expect_success 'git hook run a hook with a bad shebang' '
test_when_finished "rm -rf bad-hooks" &&
mkdir bad-hooks &&