]> git.ipfire.org Git - thirdparty/git.git/commitdiff
hook: allow disabling config hooks
authorAdrian Ratiu <adrian.ratiu@collabora.com>
Wed, 18 Feb 2026 22:23:49 +0000 (00:23 +0200)
committerJunio C Hamano <gitster@pobox.com>
Thu, 19 Feb 2026 21:23:41 +0000 (13:23 -0800)
Hooks specified via configs are always enabled, however users
might want to disable them without removing from the config,
like locally disabling a global hook.

Add a hook.<name>.enabled config which defaults to true and
can be optionally set for each configured hook.

Suggested-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/config/hook.adoc
hook.c
t/t1800-hook.sh

index 9faafe3016288cd23d255cba1c395fffa5c62ea4..0cda4745a6474e63dfe0506e1c6c140a49da9f6b 100644 (file)
@@ -13,3 +13,10 @@ hook.<name>.event::
        specified event, the associated `hook.<name>.command` is executed.
        This is a multi-valued key. To run `hook.<name>` on multiple
        events, specify the key more than once. See linkgit:git-hook[1].
+
+hook.<name>.enabled::
+       Whether the hook `hook.<name>` is enabled. Defaults to `true`.
+       Set to `false` to disable the hook without removing its
+       configuration. This is particularly useful when a hook is defined
+       in a system or global config file and needs to be disabled for a
+       specific repository. See linkgit:git-hook[1].
diff --git a/hook.c b/hook.c
index 8a9b405f76c94dba8e110728974d0c8ba12e9047..35c24bf33d73c99a2de5a644090999832e64e8d9 100644 (file)
--- a/hook.c
+++ b/hook.c
@@ -164,6 +164,21 @@ static int hook_config_lookup_all(const char *key, const char *value,
                char *old = strmap_put(&data->commands, hook_name,
                                       xstrdup(value));
                free(old);
+       } else if (!strcmp(subkey, "enabled")) {
+               switch (git_parse_maybe_bool(value)) {
+               case 0: /* disabled */
+                       if (!unsorted_string_list_lookup(&data->disabled_hooks,
+                                                        hook_name))
+                               string_list_append(&data->disabled_hooks,
+                                                  hook_name);
+                       break;
+               case 1: /* enabled: undo a prior disabled entry */
+                       unsorted_string_list_remove(&data->disabled_hooks,
+                                                   hook_name);
+                       break;
+               default:
+                       break; /* ignore unrecognised values */
+               }
        }
 
        free(hook_name);
@@ -216,6 +231,11 @@ static void build_hook_config_map(struct repository *r, struct strmap *cache)
                        const char *hname = hook_names->items[i].string;
                        char *command;
 
+                       /* filter out disabled hooks */
+                       if (unsorted_string_list_lookup(&cb_data.disabled_hooks,
+                                                       hname))
+                               continue;
+
                        command = strmap_get(&cb_data.commands, hname);
                        if (!command)
                                die(_("'hook.%s.command' must be configured or "
index f1048a5119db8db3546f9abde5dd6abb8842792c..9797802735e848368d35d9ebed13b3854da13f36 100755 (executable)
@@ -318,6 +318,38 @@ test_expect_success 'rejects hooks with no commands configured' '
        test_grep "hook.broken.command" actual
 '
 
+test_expect_success 'disabled hook is not run' '
+       test_config hook.skipped.event "test-hook" &&
+       test_config hook.skipped.command "echo \"Should not run\"" &&
+       test_config hook.skipped.enabled false &&
+
+       git hook run --ignore-missing test-hook 2>actual &&
+       test_must_be_empty actual
+'
+
+test_expect_success 'disabled hook does not appear in git hook list' '
+       test_config hook.active.event "pre-commit" &&
+       test_config hook.active.command "echo active" &&
+       test_config hook.inactive.event "pre-commit" &&
+       test_config hook.inactive.command "echo inactive" &&
+       test_config hook.inactive.enabled false &&
+
+       git hook list pre-commit >actual &&
+       test_grep "active" actual &&
+       test_grep ! "inactive" actual
+'
+
+test_expect_success 'globally disabled hook can be re-enabled locally' '
+       test_config_global hook.global-hook.event "test-hook" &&
+       test_config_global hook.global-hook.command "echo \"global-hook ran\"" &&
+       test_config_global hook.global-hook.enabled false &&
+       test_config hook.global-hook.enabled true &&
+
+       echo "global-hook ran" >expected &&
+       git hook run test-hook 2>actual &&
+       test_cmp expected actual
+'
+
 test_expect_success 'git hook run a hook with a bad shebang' '
        test_when_finished "rm -rf bad-hooks" &&
        mkdir bad-hooks &&