git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v7 04/13] hook: allow parallel hook execution

From
Adrian Ratiu <adrian.ratiu@collabora.com>
Date
Apr 10, 2026, 09:05 UTC
Message-ID
<20260410090608.75283-5-adrian.ratiu@collabora.com>
In-Reply-To
<20260410090608.75283-1-adrian.ratiu@collabora.com>
From: Emily Shaffer <nasamuffin@google.com>

Hooks always run in sequential order due to the hardcoded jobs == 1 passed to run_process_parallel(). Remove that hardcoding to allow users to run hooks in parallel (opt-in).

Users need to decide which hooks to run in parallel, by specifying "parallel = true" in the config, because Git cannot know if their specific hooks are safe to run or not in parallel (for e.g. two hooks might write to the same file or call the same program).

Some hooks are unsafe to run in parallel by design: these will marked in the next commit using RUN_HOOKS_OPT_INIT_FORCE_SERIAL.

The hook.jobs config specifies the default number of jobs applied to all hooks which have parallelism enabled.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
---
 Documentation/config/hook.adoc |  13 +++
 hook.c                         |  79 ++++++++++++++++--
 hook.h                         |  25 ++++++
 t/t1800-hook.sh                | 142 +++++++++++++++++++++++++++++++++
 4 files changed, 253 insertions(+), 6 deletions(-)
diff --git a/Documentation/config/hook.adoc b/Documentation/config/hook.adoc
index b7847f9338..21800db648 100644
--- a/Documentation/config/hook.adoc
+++ b/Documentation/config/hook.adoc
@@ -23,6 +23,19 @@ hook.<friendly-name>.enabled::
 	in a system or global config file and needs to be disabled for a
 	specific repository. See linkgit:git-hook[1].
 
+hook.<friendly-name>.parallel::
+	Whether the hook `hook.<friendly-name>` may run in parallel with other hooks
+	for the same event. Defaults to `false`. Set to `true` only when the
+	hook script is safe to run concurrently with other hooks for the same
+	event. If any hook for an event does not have this set to `true`,
+	all hooks for that event run sequentially regardless of `hook.jobs`.
+	Only configured (named) hooks need to declare this. Traditional hooks
+	found in the hooks directory do not need to, and run in parallel when
+	the effective job count is greater than 1. See linkgit:git-hook[1].
+
 hook.jobs::
 	Specifies how many hooks can be run simultaneously during parallelized
 	hook execution. If unspecified, defaults to 1 (serial execution).
++
+This setting has no effect unless all configured hooks for the event have
+`hook.<friendly-name>.parallel` set to `true`.
diff --git a/hook.c b/hook.c
index b8cce00e57..85c0de5e47 100644
--- a/hook.c
+++ b/hook.c
@@ -116,6 +116,7 @@ struct hook_config_cache_entry {
 	char *command;
 	enum config_scope scope;
 	bool disabled;
+	bool parallel;
 };
 
 /*
@@ -123,12 +124,14 @@ struct hook_config_cache_entry {
  * commands: friendly-name to command map.
  * event_hooks: event-name to list of friendly-names map.
  * disabled_hooks: set of friendly-names with hook.<friendly-name>.enabled = false.
+ * parallel_hooks: friendly-name to parallel flag.
  * jobs: value of the global hook.jobs key. Defaults to 0 if unset (stored in r->hook_jobs).
  */
 struct hook_all_config_cb {
 	struct strmap commands;
 	struct strmap event_hooks;
 	struct string_list disabled_hooks;
+	struct strmap parallel_hooks;
 	unsigned int jobs;
 };
 
@@ -219,6 +222,15 @@ static int hook_config_lookup_all(const char *key, const char *value,
 		default:
 			break; /* ignore unrecognised values */
 		}
+	} else if (!strcmp(subkey, "parallel")) {
+		int v = git_parse_maybe_bool(value);
+		if (v >= 0)
+			strmap_put(&data->parallel_hooks, hook_name,
+				   (void *)(uintptr_t)v);
+		else
+			warning(_("hook.%s.parallel must be a boolean,"
+				  " ignoring: '%s'"),
+				hook_name, value);
 	}
 
 	free(hook_name);
@@ -263,6 +275,7 @@ static void build_hook_config_map(struct repository *r, struct strmap *cache)
 	strmap_init(&cb_data.commands);
 	strmap_init(&cb_data.event_hooks);
 	string_list_init_dup(&cb_data.disabled_hooks);
+	strmap_init(&cb_data.parallel_hooks);
 
 	/* Parse all configs in one run, capturing hook.* including hook.jobs. */
 	repo_config(r, hook_config_lookup_all, &cb_data);
@@ -282,6 +295,7 @@ static void build_hook_config_map(struct repository *r, struct strmap *cache)
 			struct hook_config_cache_entry *entry;
 			char *command;
 
+			bool is_par = !!strmap_get(&cb_data.parallel_hooks, hname);
 			bool is_disabled =
 				!!unsorted_string_list_lookup(
 					&cb_data.disabled_hooks, hname);
@@ -302,6 +316,7 @@ static void build_hook_config_map(struct repository *r, struct strmap *cache)
 			entry->command = xstrdup_or_null(command);
 			entry->scope = scope;
 			entry->disabled = is_disabled;
+			entry->parallel = is_par;
 			string_list_append(hooks, hname)->util = entry;
 		}
 
@@ -312,6 +327,7 @@ static void build_hook_config_map(struct repository *r, struct strmap *cache)
 		r->hook_jobs = cb_data.jobs;
 
 	strmap_clear(&cb_data.commands, 1);
+	strmap_clear(&cb_data.parallel_hooks, 0); /* values are uintptr_t, not heap ptrs */
 	string_list_clear(&cb_data.disabled_hooks, 0);
 	strmap_for_each_entry(&cb_data.event_hooks, &iter, e) {
 		string_list_clear(e->value, 0);
@@ -389,6 +405,7 @@ static void list_hooks_add_configured(struct repository *r,
 			entry->command ? xstrdup(entry->command) : NULL;
 		hook->u.configured.scope = entry->scope;
 		hook->u.configured.disabled = entry->disabled;
+		hook->parallel = entry->parallel;
 
 		string_list_append(list, friendly_name)->util = hook;
 	}
@@ -538,21 +555,75 @@ static void run_hooks_opt_clear(struct run_hooks_opt *options)
 	strvec_clear(&options->args);
 }
 
+/* Determine how many jobs to use for hook execution. */
+static unsigned int get_hook_jobs(struct repository *r,
+				  struct run_hooks_opt *options,
+				  struct string_list *hook_list)
+{
+	/*
+	 * Hooks needing separate output streams must run sequentially.
+	 * Next commit will allow parallelizing these as well.
+	 */
+	if (!options->stdout_to_stderr)
+		return 1;
+
+	/*
+	 * An explicit job count overrides everything else: this covers both
+	 * FORCE_SERIAL callers (for hooks that must never run in parallel)
+	 * and the -j flag from the CLI. The CLI override is intentional: users
+	 * may want to serialize hooks declared parallel or to parallelize more
+	 * aggressively than the default.
+	 */
+	if (options->jobs)
+		return options->jobs;
+
+	/*
+	 * Use hook.jobs from the already-parsed config cache (in-repo), or
+	 * fallback to a direct config lookup (out-of-repo).
+	 * Default to 1 (serial execution) on failure.
+	 */
+	options->jobs = 1;
+	if (r) {
+		if (r->gitdir && r->hook_config_cache && r->hook_jobs)
+			options->jobs = r->hook_jobs;
+		else
+			repo_config_get_uint(r, "hook.jobs", &options->jobs);
+	}
+
+	/*
+	 * Cap to serial any configured hook not marked as parallel = true.
+	 * This enforces the parallel = false default, even for "traditional"
+	 * hooks from the hookdir which cannot be marked parallel = true.
+	 */
+	for (size_t i = 0; i < hook_list->nr; i++) {
+		struct hook *h = hook_list->items[i].util;
+		if (h->kind == HOOK_CONFIGURED && !h->parallel) {
+			options->jobs = 1;
+			break;
+		}
+	}
+
+	return options->jobs;
+}
+
 int run_hooks_opt(struct repository *r, const char *hook_name,
 		  struct run_hooks_opt *options)
 {
+	struct string_list *hook_list = list_hooks(r, hook_name, options);
 	struct hook_cb_data cb_data = {
 		.rc = 0,
 		.hook_name = hook_name,
+		.hook_command_list = hook_list,
 		.options = options,
 	};
 	int ret = 0;
+	unsigned int jobs = get_hook_jobs(r, options, hook_list);
 	const struct run_process_parallel_opts opts = {
 		.tr2_category = "hook",
 		.tr2_label = hook_name,
 
-		.processes = options->jobs,
-		.ungroup = options->jobs == 1,
+		.processes = jobs,
+		.ungroup = jobs == 1,
 
 		.get_next_task = pick_next_hook,
 		.start_failure = notify_start_failure,
@@ -568,9 +639,6 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
 	if (options->path_to_stdin && options->feed_pipe)
 		BUG("options path_to_stdin and feed_pipe are mutually exclusive");
 
-	if (!options->jobs)
-		BUG("run_hooks_opt must be called with options.jobs >= 1");
-
 	/*
 	 * Ensure cb_data copy and free functions are either provided together,
 	 * or neither one is provided.
@@ -581,7 +649,6 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
 	if (options->invoked_hook)
 		*options->invoked_hook = 0;
 
-	cb_data.hook_command_list = list_hooks(r, hook_name, options);
 	if (!cb_data.hook_command_list->nr) {
 		if (options->error_if_missing)
 			ret = error("cannot find a hook named %s", hook_name);
diff --git a/hook.h b/hook.h
index 5c5628dd1f..ba7056f872 100644
--- a/hook.h
+++ b/hook.h
@@ -35,6 +35,13 @@ struct hook {
 		} 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.
 	 *
@@ -72,6 +79,8 @@ struct run_hooks_opt {
 	 *
 	 * 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;
 
@@ -152,7 +161,23 @@ struct run_hooks_opt {
 	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, \
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
index 33decc66c0..a3011a01ca 100755
--- a/t/t1800-hook.sh
+++ b/t/t1800-hook.sh
@@ -21,6 +21,57 @@ setup_hookdir () {
 	test_when_finished rm -rf .git/hooks
 }
 
+# write_sentinel_hook <path> [sentinel]
+#
+# Writes a hook that marks itself as started, sleeps for a few seconds, then
+# marks itself done. The sleep must be long enough that sentinel_detector can
+# observe <sentinel>.started before <sentinel>.done appears when both hooks
+# run concurrently in parallel mode.
+write_sentinel_hook () {
+	sentinel="${2:-sentinel}"
+	write_script "$1" <<-EOF
+	touch ${sentinel}.started &&
+	sleep 2 &&
+	touch ${sentinel}.done
+	EOF
+}
+
+# sentinel_detector <sentinel> <output>
+#
+# Returns a shell command string suitable for use as hook.<name>.command.
+# The detector must be registered after the sentinel:
+# 1. In serial mode, the sentinel has completed (and <sentinel>.done exists)
+#    before the detector starts.
+# 2. In parallel mode, both run concurrently so <sentinel>.done has not appeared
+#    yet and the detector just sees <sentinel>.started.
+#
+# At start, poll until <sentinel>.started exists to absorb startup jitter, then
+# write to <output>:
+# 1. 'serial'   if <sentinel>.done exists (sentinel finished before we started),
+# 2. 'parallel' if only <sentinel>.started exists (sentinel still running),
+# 3. 'timeout'  if <sentinel>.started never appeared.
+#
+# The command ends with ':' so when git appends "$@" for hooks that receive
+# positional arguments (e.g. pre-push), the result ': "$@"' is valid shell
+# rather than a syntax error 'fi "$@"'.
+sentinel_detector () {
+	cat <<-EOF
+	i=0
+	while ! test -f ${1}.started && test \$i -lt 10; do
+	    sleep 1
+	    i=\$((i+1))
+	done
+	if test -f ${1}.done; then
+	    echo serial >${2}
+	elif test -f ${1}.started; then
+	    echo parallel >${2}
+	else
+	    echo timeout >${2}
+	fi
+	:
+	EOF
+}
+
 test_expect_success 'git hook usage' '
 	test_expect_code 129 git hook &&
 	test_expect_code 129 git hook run &&
@@ -658,4 +709,95 @@ test_expect_success 'server push-to-checkout hook expects stdout redirected to s
 	check_stdout_merged_to_stderr push-to-checkout
 '
 
+test_expect_success 'hook.jobs=1 config runs hooks in series' '
+	test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
+
+	# Use two configured hooks so the execution order is deterministic:
+	# hook-1 (sentinel) is listed before hook-2 (detector), so hook-1
+	# always runs first even in serial mode.
+	test_config hook.hook-1.event test-hook &&
+	test_config hook.hook-1.command \
+	    "touch sentinel.started; sleep 2; touch sentinel.done" &&
+	test_config hook.hook-2.event test-hook &&
+	test_config hook.hook-2.command \
+	    "$(sentinel_detector sentinel hook.order)" &&
+
+	test_config hook.jobs 1 &&
+
+	git hook run --allow-unknown-hook-name test-hook >out 2>err &&
+	echo serial >expect &&
+	test_cmp expect hook.order
+'
+
+test_expect_success 'hook.jobs=2 config runs hooks in parallel' '
+	test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
+	test_when_finished "rm -rf .git/hooks" &&
+
+	mkdir -p .git/hooks &&
+	write_sentinel_hook .git/hooks/test-hook &&
+
+	test_config hook.hook-2.event test-hook &&
+	test_config hook.hook-2.command \
+	    "$(sentinel_detector sentinel hook.order)" &&
+	test_config hook.hook-2.parallel true &&
+
+	test_config hook.jobs 2 &&
+
+	git hook run --allow-unknown-hook-name test-hook >out 2>err &&
+	echo parallel >expect &&
+	test_cmp expect hook.order
+'
+
+test_expect_success 'hook.<name>.parallel=true enables parallel execution' '
+	test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
+	test_config hook.hook-1.event test-hook &&
+	test_config hook.hook-1.command \
+	    "touch sentinel.started; sleep 2; touch sentinel.done" &&
+	test_config hook.hook-1.parallel true &&
+	test_config hook.hook-2.event test-hook &&
+	test_config hook.hook-2.command \
+	    "$(sentinel_detector sentinel hook.order)" &&
+	test_config hook.hook-2.parallel true &&
+
+	test_config hook.jobs 2 &&
+
+	git hook run --allow-unknown-hook-name test-hook >out 2>err &&
+	echo parallel >expect &&
+	test_cmp expect hook.order
+'
+
+test_expect_success 'hook.<name>.parallel=false (default) forces serial execution' '
+	test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
+	test_config hook.hook-1.event test-hook &&
+	test_config hook.hook-1.command \
+	    "touch sentinel.started; sleep 2; touch sentinel.done" &&
+	test_config hook.hook-2.event test-hook &&
+	test_config hook.hook-2.command \
+	    "$(sentinel_detector sentinel hook.order)" &&
+
+	test_config hook.jobs 2 &&
+
+	git hook run --allow-unknown-hook-name test-hook >out 2>err &&
+	echo serial >expect &&
+	test_cmp expect hook.order
+'
+
+test_expect_success 'one non-parallel hook forces the whole event to run serially' '
+	test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
+	test_config hook.hook-1.event test-hook &&
+	test_config hook.hook-1.command \
+	    "touch sentinel.started; sleep 2; touch sentinel.done" &&
+	test_config hook.hook-1.parallel true &&
+	test_config hook.hook-2.event test-hook &&
+	test_config hook.hook-2.command \
+	    "$(sentinel_detector sentinel hook.order)" &&
+	# hook-2 has no parallel=true: should force serial for all
+
+	test_config hook.jobs 2 &&
+
+	git hook run --allow-unknown-hook-name test-hook >out 2>err &&
+	echo serial >expect &&
+	test_cmp expect hook.order
+'
+
 test_done
-- 
2.52.0
Previous: Adrian RatiuNext: Adrian Ratiu
Message 104 of 113 in “Run hooks in parallel”
  1. 0/4 Run hooks in parallelAdrian Ratiu, Feb 4, 2026
  2. 1/4 config: add a repo_config_get_uint() helperAdrian Ratiu, Feb 4, 2026
  3. 3/4 hook: introduce extensions.hookStdoutToStderrAdrian Ratiu, Feb 4, 2026
  4. 2/4 hook: allow parallel hook executionAdrian Ratiu, Feb 4, 2026
  5. Patrick SteinhardtFeb 11, 2026
  6. Adrian RatiuFeb 12, 2026
  7. 4/4 hook: allow runtime enabling extensions.hookStdoutToStderrAdrian Ratiu, Feb 4, 2026
  8. Phillip WoodFeb 12, 2026
  9. Adrian RatiuFeb 12, 2026
  10. Phillip WoodFeb 13, 2026
  11. Adrian RatiuFeb 13, 2026
  12. 00/10 Run hooks in parallelAdrian Ratiu, Feb 22, 2026
  13. 02/10 config: add a repo_config_get_uint() helperAdrian Ratiu, Feb 22, 2026
  14. 03/10 hook: refactor hook_config_cache from strmap to named structAdrian Ratiu, Feb 22, 2026
  15. 04/10 hook: parse the hook.jobs configAdrian Ratiu, Feb 22, 2026
  16. 06/10 hook: mark non-parallelizable hooksAdrian Ratiu, Feb 22, 2026
  17. 01/10 repository: fix repo_init() memleak due to missing _clear()Adrian Ratiu, Feb 22, 2026
  18. 05/10 hook: allow parallel hook executionAdrian Ratiu, Feb 22, 2026
  19. 08/10 hook: add per-event jobs configAdrian Ratiu, Feb 22, 2026
  20. 09/10 hook: introduce extensions.hookStdoutToStderrAdrian Ratiu, Feb 22, 2026
  21. 10/10 hook: allow runtime enabling extensions.hookStdoutToStderrAdrian Ratiu, Feb 22, 2026
  22. 07/10 hook: add -j/--jobs option to git hook runAdrian Ratiu, Feb 22, 2026
  23. 0/9 Run hooks in parallelAdrian Ratiu, Mar 9, 2026
  24. 2/9 config: add a repo_config_get_uint() helperAdrian Ratiu, Mar 9, 2026
  25. 1/9 repository: fix repo_init() memleak due to missing _clear()Adrian Ratiu, Mar 9, 2026
  26. Junio C HamanoMar 15, 2026
  27. Junio C HamanoMar 15, 2026
  28. 3/9 hook: parse the hook.jobs configAdrian Ratiu, Mar 9, 2026
  29. Junio C HamanoMar 15, 2026
  30. 5/9 hook: mark non-parallelizable hooksAdrian Ratiu, Mar 9, 2026
  31. Junio C HamanoMar 15, 2026
  32. Adrian RatiuMar 18, 2026
  33. 6/9 hook: add -j/--jobs option to git hook runAdrian Ratiu, Mar 9, 2026
  34. Junio C HamanoMar 15, 2026
  35. Adrian RatiuMar 18, 2026
  36. 4/9 hook: allow parallel hook executionAdrian Ratiu, Mar 9, 2026
  37. Junio C HamanoMar 15, 2026
  38. Adrian RatiuMar 18, 2026
  39. 7/9 hook: add per-event jobs configAdrian Ratiu, Mar 9, 2026
  40. Junio C HamanoMar 16, 2026
  41. Adrian RatiuMar 18, 2026
  42. 8/9 hook: introduce extensions.hookStdoutToStderrAdrian Ratiu, Mar 9, 2026
  43. Junio C HamanoMar 16, 2026
  44. Adrian RatiuMar 18, 2026
  45. 9/9 hook: allow runtime enabling extensions.hookStdoutToStderrAdrian Ratiu, Mar 9, 2026
  46. 0/9 Run hooks in parallelAdrian Ratiu, Mar 20, 2026
  47. 1/9 config: add a repo_config_get_uint() helperAdrian Ratiu, Mar 20, 2026
  48. 2/9 hook: parse the hook.jobs configAdrian Ratiu, Mar 20, 2026
  49. Patrick SteinhardtMar 24, 2026
  50. Adrian RatiuMar 24, 2026
  51. 4/9 hook: allow pre-push parallel executionAdrian Ratiu, Mar 20, 2026
  52. 3/9 hook: allow parallel hook executionAdrian Ratiu, Mar 20, 2026
  53. Patrick SteinhardtMar 24, 2026
  54. 5/9 hook: mark non-parallelizable hooksAdrian Ratiu, Mar 20, 2026
  55. 6/9 hook: add -j/--jobs option to git hook runAdrian Ratiu, Mar 20, 2026
  56. Patrick SteinhardtMar 24, 2026
  57. 8/9 hook: warn when hook.<friendly-name>.jobs is setAdrian Ratiu, Mar 20, 2026
  58. Patrick SteinhardtMar 24, 2026
  59. 7/9 hook: add per-event jobs configAdrian Ratiu, Mar 20, 2026
  60. Patrick SteinhardtMar 24, 2026
  61. 9/9 hook: add hook.<event>.enabled switchAdrian Ratiu, Mar 20, 2026
  62. Patrick SteinhardtMar 24, 2026
  63. Adrian RatiuMar 25, 2026
  64. Junio C HamanoMar 20, 2026
  65. Adrian RatiuMar 23, 2026
  66. Patrick SteinhardtMar 24, 2026
  67. 00/12 Run hooks in parallelAdrian Ratiu, Mar 26, 2026
  68. 01/12 repository: fix repo_init() memleak due to missing _clear()Adrian Ratiu, Mar 26, 2026
  69. 02/12 config: add a repo_config_get_uint() helperAdrian Ratiu, Mar 26, 2026
  70. 03/12 hook: parse the hook.jobs configAdrian Ratiu, Mar 26, 2026
  71. 06/12 hook: mark non-parallelizable hooksAdrian Ratiu, Mar 26, 2026
  72. 07/12 hook: add -j/--jobs option to git hook runAdrian Ratiu, Mar 26, 2026
  73. Patrick SteinhardtMar 27, 2026
  74. 05/12 hook: allow pre-push parallel executionAdrian Ratiu, Mar 26, 2026
  75. 08/12 hook: add per-event jobs configAdrian Ratiu, Mar 26, 2026
  76. 04/12 hook: allow parallel hook executionAdrian Ratiu, Mar 26, 2026
  77. 09/12 hook: warn when hook.<friendly-name>.jobs is setAdrian Ratiu, Mar 26, 2026
  78. Patrick SteinhardtMar 27, 2026
  79. 11/12 hook: add hook.<event>.enabled switchAdrian Ratiu, Mar 26, 2026
  80. 10/12 hook: move is_known_hook() to hook.c for wider useAdrian Ratiu, Mar 26, 2026
  81. Patrick SteinhardtMar 27, 2026
  82. Adrian RatiuMar 27, 2026
  83. 12/12 hook: allow hook.jobs=-1 to use all available CPU coresAdrian Ratiu, Mar 26, 2026
  84. 00/12 Run hooks in parallelAdrian Ratiu, Apr 4, 2026
  85. 02/12 config: add a repo_config_get_uint() helperAdrian Ratiu, Apr 4, 2026
  86. 01/12 repository: fix repo_init() memleak due to missing _clear()Adrian Ratiu, Apr 4, 2026
  87. 03/12 hook: parse the hook.jobs configAdrian Ratiu, Apr 4, 2026
  88. 04/12 hook: allow parallel hook executionAdrian Ratiu, Apr 4, 2026
  89. 05/12 hook: allow pre-push parallel executionAdrian Ratiu, Apr 4, 2026
  90. 06/12 hook: mark non-parallelizable hooksAdrian Ratiu, Apr 4, 2026
  91. 07/12 hook: add -j/--jobs option to git hook runAdrian Ratiu, Apr 4, 2026
  92. 08/12 hook: add per-event jobs configAdrian Ratiu, Apr 4, 2026
  93. 09/12 hook: warn when hook.<friendly-name>.jobs is setAdrian Ratiu, Apr 4, 2026
  94. 10/12 hook: move is_known_hook() to hook.c for wider useAdrian Ratiu, Apr 4, 2026
  95. 11/12 hook: add hook.<event>.enabled switchAdrian Ratiu, Apr 4, 2026
  96. 12/12 hook: allow hook.jobs=-1 to use all available CPU coresAdrian Ratiu, Apr 4, 2026
  97. Junio C HamanoApr 6, 2026
  98. Patrick SteinhardtApr 8, 2026
  99. Junio C HamanoApr 8, 2026
  100. 00/13 Run hooks in parallelAdrian Ratiu, Apr 10, 2026
  101. 01/13 repository: fix repo_init() memleak due to missing _clear()Adrian Ratiu, Apr 10, 2026
  102. 02/13 config: add a repo_config_get_uint() helperAdrian Ratiu, Apr 10, 2026
  103. 03/13 hook: parse the hook.jobs configAdrian Ratiu, Apr 10, 2026
  104. 04/13 hook: allow parallel hook executionAdrian Ratiu, Apr 10, 2026
  105. 05/13 hook: allow pre-push parallel executionAdrian Ratiu, Apr 10, 2026
  106. 06/13 hook: mark non-parallelizable hooksAdrian Ratiu, Apr 10, 2026
  107. 07/13 hook: add -j/--jobs option to git hook runAdrian Ratiu, Apr 10, 2026
  108. 08/13 hook: add per-event jobs configAdrian Ratiu, Apr 10, 2026
  109. 09/13 hook: warn when hook.<friendly-name>.jobs is setAdrian Ratiu, Apr 10, 2026
  110. 10/13 hook: move is_known_hook() to hook.c for wider useAdrian Ratiu, Apr 10, 2026
  111. 11/13 hook: add hook.<event>.enabled switchAdrian Ratiu, Apr 10, 2026
  112. 12/13 hook: allow hook.jobs=-1 to use all available CPU coresAdrian Ratiu, Apr 10, 2026
  113. 13/13 t1800: test SIGPIPE with parallel hooksAdrian Ratiu, Apr 10, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.