Skip to content

Commit 905b034

Browse files
10ne1gitster
authored andcommitted
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. Reuses is_friendly_name() from the previous commit to distinguish event names from friendly-names when processing .enabled settings. 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>
1 parent cb406de commit 905b034

7 files changed

Lines changed: 121 additions & 9 deletions

File tree

Documentation/config/hook.adoc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ hook.<friendly-name>.parallel::
3333
found in the hooks directory do not need to, and run in parallel when
3434
the effective job count is greater than 1. See linkgit:git-hook[1].
3535

36+
hook.<event>.enabled::
37+
Switch to enable or disable all hooks for the `<event>` hook event.
38+
When set to `false`, no hooks fire for that event, regardless of any
39+
per-hook `hook.<friendly-name>.enabled` settings. Defaults to `true`.
40+
See linkgit:git-hook[1].
41+
+
42+
Note on naming: `<event>` must be the event name (e.g. `pre-commit`),
43+
not a hook friendly-name. A name that also carries `.command`, `.event`,
44+
or `.parallel` is treated as a friendly-name and its `.enabled` value
45+
applies only to that individual hook. See `hook.<friendly-name>.enabled`
46+
above.
47+
3648
hook.<event>.jobs::
3749
Specifies how many hooks can be run simultaneously for the `<event>`
3850
hook event (e.g. `hook.post-receive.jobs = 4`). Overrides `hook.jobs`

builtin/hook.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,22 @@ static int list(int argc, const char **argv, const char *prefix,
7777
const char *name = h->u.configured.friendly_name;
7878
const char *scope = show_scope ?
7979
config_scope_name(h->u.configured.scope) : NULL;
80+
/*
81+
* Show the most relevant disable reason. Event-level
82+
* takes precedence: if the whole event is off, that
83+
* is what the user needs to know. The per-hook
84+
* "disabled" surfaces once the event is re-enabled.
85+
*/
86+
const char *disability =
87+
h->u.configured.event_disabled ? "event-disabled\t" :
88+
h->u.configured.disabled ? "disabled\t" :
89+
"";
8090
if (scope)
81-
printf("%s\t%s%s%c", scope,
82-
h->u.configured.disabled ? "disabled\t" : "",
83-
name, line_terminator);
91+
printf("%s\t%s%s%c", scope, disability, name,
92+
line_terminator);
8493
else
85-
printf("%s%s%c",
86-
h->u.configured.disabled ? "disabled\t" : "",
87-
name, line_terminator);
94+
printf("%s%s%c", disability, name,
95+
line_terminator);
8896
break;
8997
}
9098
default:

hook.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ struct hook_config_cache_entry {
127127
* Callback struct to collect all hook.* keys in a single config pass.
128128
* commands: friendly-name to command map.
129129
* event_hooks: event-name to list of friendly-names map.
130-
* disabled_hooks: set of friendly-names with hook.<friendly-name>.enabled = false.
130+
* disabled_hooks: set of all names with hook.<name>.enabled = false; after
131+
* parsing, names that are not friendly-names become event-level
132+
* disables stored in cache->event_disabled. This collects all.
131133
* parallel_hooks: friendly-name to parallel flag.
132134
* event_jobs: event-name to per-event jobs count (stored as uintptr_t, NULL == unset).
133135
* jobs: value of the global hook.jobs key. Defaults to 0 if unset (stored in r->hook_jobs).
@@ -332,6 +334,22 @@ static void build_hook_config_map(struct repository *r, struct strmap *cache)
332334

333335
warn_jobs_on_friendly_names(&cb_data);
334336

337+
/*
338+
* Populate event_disabled: names in disabled_hooks that are not
339+
* friendly-names are event-level switches (hook.<event>.enabled = false).
340+
* Names that are friendly-names are already handled per-hook via the
341+
* hook_config_cache_entry.disabled flag below.
342+
*/
343+
if (r) {
344+
string_list_clear(&r->event_disabled, 0);
345+
string_list_init_dup(&r->event_disabled);
346+
for (size_t i = 0; i < cb_data.disabled_hooks.nr; i++) {
347+
const char *n = cb_data.disabled_hooks.items[i].string;
348+
if (!is_friendly_name(&cb_data, n))
349+
string_list_append(&r->event_disabled, n);
350+
}
351+
}
352+
335353
/* Construct the cache from parsed configs. */
336354
strmap_for_each_entry(&cb_data.event_hooks, &iter, e) {
337355
struct string_list *hook_names = e->value;
@@ -433,6 +451,8 @@ static void list_hooks_add_configured(struct repository *r,
433451
{
434452
struct strmap *cache = get_hook_config_cache(r);
435453
struct string_list *configured_hooks = strmap_get(cache, hookname);
454+
int event_is_disabled = r ? !!unsorted_string_list_lookup(&r->event_disabled,
455+
hookname) : 0;
436456

437457
/* Iterate through configured hooks and initialize internal states */
438458
for (size_t i = 0; configured_hooks && i < configured_hooks->nr; i++) {
@@ -458,6 +478,7 @@ static void list_hooks_add_configured(struct repository *r,
458478
entry->command ? xstrdup(entry->command) : NULL;
459479
hook->u.configured.scope = entry->scope;
460480
hook->u.configured.disabled = entry->disabled;
481+
hook->u.configured.event_disabled = event_is_disabled;
461482
hook->parallel = entry->parallel;
462483

463484
string_list_append(list, friendly_name)->util = hook;
@@ -470,6 +491,8 @@ static void list_hooks_add_configured(struct repository *r,
470491
if (!r || !r->gitdir) {
471492
hook_cache_clear(cache);
472493
free(cache);
494+
if (r)
495+
string_list_clear(&r->event_disabled, 0);
473496
}
474497
}
475498

@@ -501,7 +524,7 @@ int hook_exists(struct repository *r, const char *name)
501524
for (size_t i = 0; i < hooks->nr; i++) {
502525
struct hook *h = hooks->items[i].util;
503526
if (h->kind == HOOK_TRADITIONAL ||
504-
!h->u.configured.disabled) {
527+
(!h->u.configured.disabled && !h->u.configured.event_disabled)) {
505528
exists = 1;
506529
break;
507530
}
@@ -524,7 +547,8 @@ static int pick_next_hook(struct child_process *cp,
524547
if (hook_cb->hook_to_run_index >= hook_list->nr)
525548
return 0;
526549
h = hook_list->items[hook_cb->hook_to_run_index++].util;
527-
} while (h->kind == HOOK_CONFIGURED && h->u.configured.disabled);
550+
} while (h->kind == HOOK_CONFIGURED &&
551+
(h->u.configured.disabled || h->u.configured.event_disabled));
528552

529553
cp->no_stdin = 1;
530554
strvec_pushv(&cp->env, hook_cb->options->env.v);

hook.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ struct hook {
3232
const char *command;
3333
enum config_scope scope;
3434
unsigned int disabled:1;
35+
unsigned int event_disabled:1;
3536
} configured;
3637
} u;
3738

repository.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ void repo_clear(struct repository *repo)
420420
FREE_AND_NULL(repo->hook_config_cache);
421421
}
422422
strmap_clear(&repo->event_jobs, 0); /* values are uintptr_t, not heap ptrs */
423+
string_list_clear(&repo->event_disabled, 0);
423424

424425
if (repo->promisor_remote_config) {
425426
promisor_remote_clear(repo->promisor_remote_config);

repository.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define REPOSITORY_H
33

44
#include "strmap.h"
5+
#include "string-list.h"
56
#include "repo-settings.h"
67
#include "environment.h"
78

@@ -178,6 +179,9 @@ struct repository {
178179
/* Cached map of event-name -> jobs count (as uintptr_t) from hook.<event>.jobs. */
179180
struct strmap event_jobs;
180181

182+
/* Cached list of event names with hook.<event>.enabled = false. */
183+
struct string_list event_disabled;
184+
181185
/* Configurations related to promisor remotes. */
182186
char *repository_format_partial_clone;
183187
struct promisor_remote_config *promisor_remote_config;

t/t1800-hook.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,4 +1026,66 @@ test_expect_success 'hook.<event>.jobs does not warn for a real event name' '
10261026
test_grep ! "friendly-name" err
10271027
'
10281028

1029+
test_expect_success 'hook.<event>.enabled=false skips all hooks for event' '
1030+
test_config hook.hook-1.event test-hook &&
1031+
test_config hook.hook-1.command "echo ran" &&
1032+
test_config hook.test-hook.enabled false &&
1033+
git hook run test-hook >out 2>err &&
1034+
test_must_be_empty out
1035+
'
1036+
1037+
test_expect_success 'hook.<event>.enabled=true does not suppress hooks' '
1038+
test_config hook.hook-1.event test-hook &&
1039+
test_config hook.hook-1.command "echo ran" &&
1040+
test_config hook.test-hook.enabled true &&
1041+
git hook run test-hook >out 2>err &&
1042+
test_grep "ran" err
1043+
'
1044+
1045+
test_expect_success 'hook.<event>.enabled=false does not affect other events' '
1046+
test_config hook.hook-1.event test-hook &&
1047+
test_config hook.hook-1.command "echo ran" &&
1048+
test_config hook.other-event.enabled false &&
1049+
git hook run test-hook >out 2>err &&
1050+
test_grep "ran" err
1051+
'
1052+
1053+
test_expect_success 'hook.<friendly-name>.enabled=false still disables that hook' '
1054+
test_config hook.hook-1.event test-hook &&
1055+
test_config hook.hook-1.command "echo hook-1" &&
1056+
test_config hook.hook-2.event test-hook &&
1057+
test_config hook.hook-2.command "echo hook-2" &&
1058+
test_config hook.hook-1.enabled false &&
1059+
git hook run test-hook >out 2>err &&
1060+
test_grep ! "hook-1" err &&
1061+
test_grep "hook-2" err
1062+
'
1063+
1064+
test_expect_success 'git hook list shows event-disabled hooks as event-disabled' '
1065+
test_config hook.hook-1.event test-hook &&
1066+
test_config hook.hook-1.command "echo ran" &&
1067+
test_config hook.hook-2.event test-hook &&
1068+
test_config hook.hook-2.command "echo ran" &&
1069+
test_config hook.test-hook.enabled false &&
1070+
git hook list test-hook >actual &&
1071+
test_grep "^event-disabled hook-1$" actual &&
1072+
test_grep "^event-disabled hook-2$" actual
1073+
'
1074+
1075+
test_expect_success 'git hook list shows scope with event-disabled' '
1076+
test_config hook.hook-1.event test-hook &&
1077+
test_config hook.hook-1.command "echo ran" &&
1078+
test_config hook.test-hook.enabled false &&
1079+
git hook list --show-scope test-hook >actual &&
1080+
test_grep "^local event-disabled hook-1$" actual
1081+
'
1082+
1083+
test_expect_success 'git hook list still shows hooks when event is disabled' '
1084+
test_config hook.hook-1.event test-hook &&
1085+
test_config hook.hook-1.command "echo ran" &&
1086+
test_config hook.test-hook.enabled false &&
1087+
git hook list test-hook >actual &&
1088+
test_grep "event-disabled" actual
1089+
'
1090+
10291091
test_done

0 commit comments

Comments
 (0)