Skip to content

Commit ee222e7

Browse files
committed
Merge branch 'ar/parallel-hooks' into seen
* ar/parallel-hooks: hook: allow runtime enabling extensions.hookStdoutToStderr hook: introduce extensions.hookStdoutToStderr hook: allow parallel hook execution config: add a repo_config_get_uint() helper
2 parents 86cf051 + 1fc9744 commit ee222e7

24 files changed

+476
-43
lines changed

Documentation/config/extensions.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,21 @@ The extension can be enabled automatically for new repositories by setting
116116
`init.defaultSubmodulePathConfig` to `true`, for example by running
117117
`git config --global init.defaultSubmodulePathConfig true`.
118118
119+
hookStdoutToStderr:::
120+
If enabled, the stdout of all hooks is redirected to stderr. This
121+
enforces consistency, since by default most hooks already behave
122+
this way, with pre-push being the only known exception.
123+
+
124+
This is useful for parallel hook execution (see the `hook.jobs` config in
125+
linkgit:git-config[1]), as it allows the output of multiple hooks running
126+
in parallel to be grouped (de-interleaved) correctly.
127+
+
128+
Defaults to disabled. When disabled, `hook.jobs` has no effect for pre-push
129+
hooks, which will always be run sequentially.
130+
+
131+
The extension can also be enabled by setting `hook.forceStdoutToStderr`
132+
to `true` in the global configuration.
133+
119134
worktreeConfig:::
120135
If enabled, then worktrees will load config settings from the
121136
`$GIT_DIR/config.worktree` file in addition to the

Documentation/config/hook.adoc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,17 @@ hook.<name>.enabled::
2222
configuration. This is particularly useful when a hook is defined
2323
in a system or global config file and needs to be disabled for a
2424
specific repository. See linkgit:git-hook[1].
25+
26+
hook.jobs::
27+
Specifies how many hooks can be run simultaneously during parallelized
28+
hook execution. If unspecified, defaults to the number of processors on
29+
the current system.
30+
+
31+
This has no effect for hooks requiring separate output streams (like `pre-push`)
32+
unless `extensions.hookStdoutToStderr` is enabled.
33+
34+
hook.forceStdoutToStderr::
35+
A boolean that enables the `extensions.hookStdoutToStderr` behavior
36+
(merging stdout to stderr for all hooks) globally. This effectively
37+
forces all hooks to behave as if the extension was enabled, allowing
38+
parallel execution for hooks like `pre-push`.

Documentation/git-hook.adoc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ git-hook - Run git hooks
88
SYNOPSIS
99
--------
1010
[verse]
11-
'git hook' run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]
11+
'git hook' run [--ignore-missing] [--to-stdin=<path>] [(-j|--jobs) <n>]
12+
<hook-name> [-- <hook-args>]
1213
'git hook' list [-z] <hook-name>
1314

1415
DESCRIPTION
@@ -134,6 +135,16 @@ OPTIONS
134135
-z::
135136
Terminate "list" output lines with NUL instead of newlines.
136137

138+
-j::
139+
--jobs::
140+
Only valid for `run`.
141+
+
142+
Specify how many hooks to run simultaneously. If this flag is not specified,
143+
the value of the `hook.jobs` config is used, see linkgit:git-config[1]. If the
144+
config is not specified, the number of CPUs on the current system is used. Some
145+
hooks may be ineligible for parallelization: for example, 'commit-msg' hooks
146+
typically modify the commit message body and cannot be parallelized.
147+
137148
WRAPPERS
138149
--------
139150
@@ -157,6 +168,7 @@ git hook run mywrapper-start-tests \
157168
# providing something to stdin
158169
--stdin some-tempfile-123 \
159170
# execute hooks in serial
171+
--jobs 1 \
160172
# plus some arguments of your own...
161173
-- \
162174
--testname bar \

builtin/am.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -490,9 +490,11 @@ static int run_applypatch_msg_hook(struct am_state *state)
490490

491491
assert(state->msg);
492492

493-
if (!state->no_verify)
494-
ret = run_hooks_l(the_repository, "applypatch-msg",
495-
am_path(state, "final-commit"), NULL);
493+
if (!state->no_verify) {
494+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SERIAL;
495+
strvec_push(&opt.args, am_path(state, "final-commit"));
496+
ret = run_hooks_opt(the_repository, "applypatch-msg", &opt);
497+
}
496498

497499
if (!ret) {
498500
FREE_AND_NULL(state->msg);
@@ -509,7 +511,7 @@ static int run_applypatch_msg_hook(struct am_state *state)
509511
*/
510512
static int run_post_rewrite_hook(const struct am_state *state)
511513
{
512-
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
514+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_PARALLEL;
513515

514516
strvec_push(&opt.args, "rebase");
515517
opt.path_to_stdin = am_path(state, "rewritten");

builtin/checkout.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "resolve-undo.h"
3232
#include "revision.h"
3333
#include "setup.h"
34+
#include "strvec.h"
3435
#include "submodule.h"
3536
#include "symlinks.h"
3637
#include "trace2.h"
@@ -123,13 +124,17 @@ static void branch_info_release(struct branch_info *info)
123124
static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
124125
int changed)
125126
{
126-
return run_hooks_l(the_repository, "post-checkout",
127-
oid_to_hex(old_commit ? &old_commit->object.oid : null_oid(the_hash_algo)),
128-
oid_to_hex(new_commit ? &new_commit->object.oid : null_oid(the_hash_algo)),
129-
changed ? "1" : "0", NULL);
127+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SERIAL;
128+
130129
/* "new_commit" can be NULL when checking out from the index before
131130
a commit exists. */
131+
strvec_pushl(&opt.args,
132+
oid_to_hex(old_commit ? &old_commit->object.oid : null_oid(the_hash_algo)),
133+
oid_to_hex(new_commit ? &new_commit->object.oid : null_oid(the_hash_algo)),
134+
changed ? "1" : "0",
135+
NULL);
132136

137+
return run_hooks_opt(the_repository, "post-checkout", &opt);
133138
}
134139

135140
static int update_some(const struct object_id *oid, struct strbuf *base,

builtin/clone.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,7 @@ static int checkout(int submodule_progress,
647647
struct tree *tree;
648648
struct tree_desc t;
649649
int err = 0;
650+
struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_SERIAL;
650651

651652
if (option_no_checkout)
652653
return 0;
@@ -697,8 +698,9 @@ static int checkout(int submodule_progress,
697698
if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK))
698699
die(_("unable to write new index file"));
699700

700-
err |= run_hooks_l(the_repository, "post-checkout", oid_to_hex(null_oid(the_hash_algo)),
701-
oid_to_hex(&oid), "1", NULL);
701+
strvec_pushl(&hook_opt.args, oid_to_hex(null_oid(the_hash_algo)),
702+
oid_to_hex(&oid), "1", NULL);
703+
err |= run_hooks_opt(the_repository, "post-checkout", &hook_opt);
702704

703705
if (!err && (option_recurse_submodules.nr > 0)) {
704706
struct child_process cmd = CHILD_PROCESS_INIT;

builtin/hook.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
#include "abspath.h"
1010

1111
#define BUILTIN_HOOK_RUN_USAGE \
12-
N_("git hook run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]")
12+
N_("git hook run [--ignore-missing] [--to-stdin=<path>] [(-j|--jobs) <n>]\n" \
13+
"<hook-name> [-- <hook-args>]")
1314
#define BUILTIN_HOOK_LIST_USAGE \
1415
N_("git hook list [-z] <hook-name>")
1516

@@ -89,14 +90,16 @@ static int run(int argc, const char **argv, const char *prefix,
8990
struct repository *repo UNUSED)
9091
{
9192
int i;
92-
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
93+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_PARALLEL;
9394
int ignore_missing = 0;
9495
const char *hook_name;
9596
struct option run_options[] = {
9697
OPT_BOOL(0, "ignore-missing", &ignore_missing,
9798
N_("silently ignore missing requested <hook-name>")),
9899
OPT_STRING(0, "to-stdin", &opt.path_to_stdin, N_("path"),
99100
N_("file to read into hooks' stdin")),
101+
OPT_UNSIGNED('j', "jobs", &opt.jobs,
102+
N_("run up to <n> hooks simultaneously")),
100103
OPT_END(),
101104
};
102105
int ret;

builtin/receive-pack.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,7 @@ static int run_receive_hook(struct command *commands,
926926
int skip_broken,
927927
const struct string_list *push_options)
928928
{
929-
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
929+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_PARALLEL;
930930
struct command *iter = commands;
931931
struct receive_hook_feed_state feed_init_state = { 0 };
932932
struct async sideband_async;
@@ -974,7 +974,7 @@ static int run_receive_hook(struct command *commands,
974974

975975
static int run_update_hook(struct command *cmd)
976976
{
977-
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
977+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_PARALLEL;
978978
struct async sideband_async;
979979
int sideband_async_started = 0;
980980
int saved_stderr = -1;
@@ -1453,7 +1453,8 @@ static const char *push_to_checkout(unsigned char *hash,
14531453
struct strvec *env,
14541454
const char *work_tree)
14551455
{
1456-
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
1456+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SERIAL;
1457+
14571458
opt.invoked_hook = invoked_hook;
14581459

14591460
strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
@@ -1668,7 +1669,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
16681669

16691670
static void run_update_post_hook(struct command *commands)
16701671
{
1671-
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
1672+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_PARALLEL;
16721673
struct async sideband_async;
16731674
struct command *cmd;
16741675
int sideband_async_started = 0;

builtin/worktree.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ static int add_worktree(const char *path, const char *refname,
609609
* is_junk is cleared, but do return appropriate code when hook fails.
610610
*/
611611
if (!ret && opts->checkout && !opts->orphan) {
612-
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
612+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SERIAL;
613613

614614
strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
615615
strvec_pushl(&opt.args,

commit.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1979,7 +1979,7 @@ size_t ignored_log_message_bytes(const char *buf, size_t len)
19791979
int run_commit_hook(int editor_is_used, const char *index_file,
19801980
int *invoked_hook, const char *name, ...)
19811981
{
1982-
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
1982+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SERIAL;
19831983
va_list args;
19841984
const char *arg;
19851985

0 commit comments

Comments
 (0)