Re: [PATCH v7 11/12] receive-pack: convert update hooks to new API
From: Emily Shaffer <hidden>
Date: 2026-01-27 00:13:07
On Wed, Jan 21, 2026 at 1:55 PM Adrian Ratiu [off-list ref] wrote:
quoted hunk ↗ jump to hunk
The hook API avoids creating a custom struct child_process and other internal hook plumbing (e.g. calling find_hook()) and prepares for the specification of hooks via configs or running parallel hooks. Execution is still sequential through the run_hooks_opt .jobs == 1, which is the unchanged default for all hooks. When jobs==1 the async muxer thread reads the hook stderr and writes to sideband 2, so run-command's poll loop is avoided and there's no need for ungroup=0 when running sequentially (Jeff's suggestion). When running in parallel, run-command with ungroup=0 will capture and de-interleave the output of each hook, then write to the parent stderr which is redirected via dup2 to the sideband muxer, so that parallel hook output is presented clearly to the client. Suggested-by: Jeff King <redacted> Signed-off-by: Emily Shaffer <redacted> Signed-off-by: Ævar Arnfjörð Bjarmason <redacted> Signed-off-by: Adrian Ratiu <redacted> --- builtin/receive-pack.c | 91 +++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 36 deletions(-)diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 9c49174616..bcd019786e 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c@@ -941,29 +941,41 @@ static int run_receive_hook(struct command *commands, static int run_update_hook(struct command *cmd) { - struct child_process proc = CHILD_PROCESS_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct async muxer; int code; - const char *hook_path = find_hook(the_repository, "update"); + int saved_stderr = -1; + int muxer_started = 0; - if (!hook_path) - return 0; + strvec_pushl(&opt.args, + cmd->ref_name, + oid_to_hex(&cmd->old_oid), + oid_to_hex(&cmd->new_oid), + NULL); - strvec_push(&proc.args, hook_path); - strvec_push(&proc.args, cmd->ref_name); - strvec_push(&proc.args, oid_to_hex(&cmd->old_oid)); - strvec_push(&proc.args, oid_to_hex(&cmd->new_oid)); + if (use_sideband) { + memset(&muxer, 0, sizeof(muxer)); + muxer.proc = copy_to_sideband; + muxer.in = -1; + if (!start_async(&muxer)) { + muxer_started = 1; + saved_stderr = dup(STDERR_FILENO); + if (saved_stderr >= 0) + dup2(muxer.in, STDERR_FILENO); + close(muxer.in); + } + } - proc.no_stdin = 1; - proc.stdout_to_stderr = 1; - proc.err = use_sideband ? -1 : 0; - proc.trace2_hook_name = "update"; + code = run_hooks_opt(the_repository, "update", &opt); - code = start_command(&proc); - if (code) - return code; - if (use_sideband) - copy_to_sideband(proc.err, -1, NULL); - return finish_command(&proc); + if (saved_stderr >= 0) { + dup2(saved_stderr, STDERR_FILENO); + close(saved_stderr); + } + if (muxer_started) + finish_async(&muxer); + + return code; } static struct command *find_command_by_refname(struct command *list,@@ -1639,34 +1651,41 @@ static const char *update(struct command *cmd, struct shallow_info *si) static void run_update_post_hook(struct command *commands) { + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct async muxer; struct command *cmd; - struct child_process proc = CHILD_PROCESS_INIT; - const char *hook; - - hook = find_hook(the_repository, "post-update"); - if (!hook) - return; + int saved_stderr = -1; + int muxer_started = 0; for (cmd = commands; cmd; cmd = cmd->next) { if (cmd->error_string || cmd->did_not_exist) continue; - if (!proc.args.nr) - strvec_push(&proc.args, hook); - strvec_push(&proc.args, cmd->ref_name); + strvec_push(&opt.args, cmd->ref_name); } - if (!proc.args.nr) + if (!opt.args.nr) return; - proc.no_stdin = 1; - proc.stdout_to_stderr = 1; - proc.err = use_sideband ? -1 : 0; - proc.trace2_hook_name = "post-update"; + if (use_sideband) { + memset(&muxer, 0, sizeof(muxer)); + muxer.proc = copy_to_sideband; + muxer.in = -1; + if (!start_async(&muxer)) { + muxer_started = 1; + saved_stderr = dup(STDERR_FILENO); + if (saved_stderr >= 0) + dup2(muxer.in, STDERR_FILENO); + close(muxer.in); + } + } - if (!start_command(&proc)) { - if (use_sideband) - copy_to_sideband(proc.err, -1, NULL); - finish_command(&proc); + run_hooks_opt(the_repository, "post-update", &opt); + + if (saved_stderr >= 0) { + dup2(saved_stderr, STDERR_FILENO); + close(saved_stderr); } + if (muxer_started) + finish_async(&muxer);
I guess I'm confused about how the muxer is working here. I guess I would expect an async to get created for each child, then to dump the entire output of the child when that child terminates, but it seems like you're setting up the async to capture the output of all the hooks (that is, it seems like start_async() and finish_async() encapsulate run_hooks_opt(), which runs multiple children...?)
} static void check_aliased_update_internal(struct command *cmd, -- 2.52.0.732.gb351b5166d.dirty