hook: warn when hook.<friendly-name>.jobs is set

Issue a warning when the user confuses the hook process and event namespaces by setting hook.<friendly-name>.jobs. Detect this by checking whether the name carrying .jobs also has .command, .event, or .parallel configured. Extract is_friendly_name() as a helper for this check, to be reused by future per-event config handling. 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>

Adrian Ratiu committed Apr 10, 2026 at 12:06 UTC 5e57b209ff21bf1087dd8539c458737c89b03150
2 files changed +70
hook.c
+40
@@ -279,6 +279,44 @@ void hook_cache_clear(struct strmap *cache)
279 strmap_clear(cache, 0);
280 }
281
282 +/*
283 + * Return true if `name` is a hook friendly-name, i.e. it has at least one of
284 + * .command, .event, or .parallel configured. These are the reliable clues
285 + * that distinguish a friendly-name from an event name. Note: .enabled is
286 + * deliberately excluded because it can appear under both namespaces.
287 + */
288 +static int is_friendly_name(struct hook_all_config_cb *cb, const char *name)
289 +{
290 + struct hashmap_iter iter;
291 + struct strmap_entry *e;
292 +
293 + if (strmap_get(&cb->commands, name) || strmap_get(&cb->parallel_hooks, name))
294 + return 1;
295 +
296 + strmap_for_each_entry(&cb->event_hooks, &iter, e) {
297 + if (unsorted_string_list_lookup(e->value, name))
298 + return 1;
299 + }
300 +
301 + return 0;
302 +}
303 +
304 +/* Warn if any name in event_jobs is also a hook friendly-name. */
305 +static void warn_jobs_on_friendly_names(struct hook_all_config_cb *cb_data)
306 +{
307 + struct hashmap_iter iter;
308 + struct strmap_entry *e;
309 +
310 + strmap_for_each_entry(&cb_data->event_jobs, &iter, e) {
311 + if (is_friendly_name(cb_data, e->key))
312 + warning(_("hook.%s.jobs is set but '%s' looks like a "
313 + "hook friendly-name, not an event name; "
314 + "hook.<event>.jobs uses the event name "
315 + "(e.g. hook.post-receive.jobs), so this "
316 + "setting will be ignored"), e->key, e->key);
317 + }
318 +}
319 +
320 /* Populate `cache` with the complete hook configuration */
321 static void build_hook_config_map(struct repository *r, struct strmap *cache)
322 {
@@ -295,6 +333,8 @@ static void build_hook_config_map(struct repository *r, struct strmap *cache)
333 /* Parse all configs in one run, capturing hook.* including hook.jobs. */
334 repo_config(r, hook_config_lookup_all, &cb_data);
335
336 + warn_jobs_on_friendly_names(&cb_data);
337 +
338 /* Construct the cache from parsed configs. */
339 strmap_for_each_entry(&cb_data.event_hooks, &iter, e) {
340 struct string_list *hook_names = e->value;
t/t1800-hook.sh
+30
@@ -1028,4 +1028,34 @@ test_expect_success 'hook.<event>.jobs still requires hook.<name>.parallel=true'
1028 test_cmp expect hook.order
1029 '
1030
1031 +test_expect_success 'hook.<friendly-name>.jobs warns when name has .command' '
1032 + test_config hook.my-hook.command "true" &&
1033 + test_config hook.my-hook.jobs 2 &&
1034 + git hook run --allow-unknown-hook-name --ignore-missing test-hook >out 2>err &&
1035 + test_grep "hook.my-hook.jobs.*friendly-name" err
1036 +'
1037 +
1038 +test_expect_success 'hook.<friendly-name>.jobs warns when name has .event' '
1039 + test_config hook.my-hook.event test-hook &&
1040 + test_config hook.my-hook.command "true" &&
1041 + test_config hook.my-hook.jobs 2 &&
1042 + git hook run --allow-unknown-hook-name --ignore-missing test-hook >out 2>err &&
1043 + test_grep "hook.my-hook.jobs.*friendly-name" err
1044 +'
1045 +
1046 +test_expect_success 'hook.<friendly-name>.jobs warns when name has .parallel' '
1047 + test_config hook.my-hook.event test-hook &&
1048 + test_config hook.my-hook.command "true" &&
1049 + test_config hook.my-hook.parallel true &&
1050 + test_config hook.my-hook.jobs 2 &&
1051 + git hook run --allow-unknown-hook-name --ignore-missing test-hook >out 2>err &&
1052 + test_grep "hook.my-hook.jobs.*friendly-name" err
1053 +'
1054 +
1055 +test_expect_success 'hook.<event>.jobs does not warn for a real event name' '
1056 + test_config hook.test-hook.jobs 2 &&
1057 + git hook run --allow-unknown-hook-name --ignore-missing test-hook >out 2>err &&
1058 + test_grep ! "friendly-name" err
1059 +'
1060 +
1061 test_done