Thread (152 messages) 152 messages, 8 authors, 2026-02-02

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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help