From 26238496a70f084912924d2c3af828c24bceb4aa Mon Sep 17 00:00:00 2001 From: Emily Shaffer Date: Fri, 26 Dec 2025 14:23:26 +0200 Subject: hook: provide stdin via callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a callback mechanism for feeding stdin to hooks alongside the existing path_to_stdin (which slurps a file's content to stdin). The advantage of this new callback is that it can feed stdin without going through the FS layer. This helps when feeding large amount of data and uses the run-command parallel stdin callback introduced in the preceding commit. Signed-off-by: Emily Shaffer Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Adrian Ratiu Signed-off-by: Junio C Hamano --- hook.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'hook.c') diff --git a/hook.c b/hook.c index b3de1048bf..5ddd7678d1 100644 --- a/hook.c +++ b/hook.c @@ -55,7 +55,7 @@ int hook_exists(struct repository *r, const char *name) static int pick_next_hook(struct child_process *cp, struct strbuf *out UNUSED, void *pp_cb, - void **pp_task_cb UNUSED) + void **pp_task_cb) { struct hook_cb_data *hook_cb = pp_cb; const char *hook_path = hook_cb->hook_path; @@ -65,11 +65,22 @@ static int pick_next_hook(struct child_process *cp, cp->no_stdin = 1; strvec_pushv(&cp->env, hook_cb->options->env.v); + + if (hook_cb->options->path_to_stdin && hook_cb->options->feed_pipe) + BUG("options path_to_stdin and feed_pipe are mutually exclusive"); + /* reopen the file for stdin; run_command closes it. */ if (hook_cb->options->path_to_stdin) { cp->no_stdin = 0; cp->in = xopen(hook_cb->options->path_to_stdin, O_RDONLY); } + + if (hook_cb->options->feed_pipe) { + cp->no_stdin = 0; + /* start_command() will allocate a pipe / stdin fd for us */ + cp->in = -1; + } + cp->stdout_to_stderr = 1; cp->trace2_hook_name = hook_cb->hook_name; cp->dir = hook_cb->options->dir; @@ -77,6 +88,12 @@ static int pick_next_hook(struct child_process *cp, strvec_push(&cp->args, hook_path); strvec_pushv(&cp->args, hook_cb->options->args.v); + /* + * Provide per-hook internal state via task_cb for easy access, so + * hook callbacks don't have to go through hook_cb->options. + */ + *pp_task_cb = hook_cb->options->feed_pipe_cb_data; + /* * This pick_next_hook() will be called again, we're only * running one hook, so indicate that no more work will be @@ -140,6 +157,7 @@ int run_hooks_opt(struct repository *r, const char *hook_name, .get_next_task = pick_next_hook, .start_failure = notify_start_failure, + .feed_pipe = options->feed_pipe, .task_finished = notify_hook_finished, .data = &cb_data, @@ -148,6 +166,9 @@ int run_hooks_opt(struct repository *r, const char *hook_name, if (!options) BUG("a struct run_hooks_opt must be provided to run_hooks"); + if (options->path_to_stdin && options->feed_pipe) + BUG("options path_to_stdin and feed_pipe are mutually exclusive"); + if (options->invoked_hook) *options->invoked_hook = 0; -- cgit v1.3 From 857f047e40f796aa43c6e7c754d8a32ee64e4f4d Mon Sep 17 00:00:00 2001 From: Adrian Ratiu Date: Fri, 26 Dec 2025 14:23:30 +0200 Subject: hook: allow overriding the ungroup option When calling run_process_parallel() in run_hooks_opt(), the ungroup option is currently hardcoded to .ungroup = 1. This causes problems when ungrouping should be disabled, for example when sideband-reading collated output from child hooks, because sideband-reading and ungrouping are mutually exclusive. Thus a new hook.h option is added to allow overriding. The existing ungroup=1 behavior is preserved in the run_hooks() API and the "hook run" command. We could modify these to take an option if necessary, so I added two code comments there. Signed-off-by: Adrian Ratiu Signed-off-by: Junio C Hamano --- builtin/hook.c | 6 ++++++ commit.c | 3 +++ hook.c | 5 ++++- hook.h | 5 +++++ 4 files changed, 18 insertions(+), 1 deletion(-) (limited to 'hook.c') diff --git a/builtin/hook.c b/builtin/hook.c index 7afec380d2..73e7b8c2e8 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -43,6 +43,12 @@ static int run(int argc, const char **argv, const char *prefix, if (!argc) goto usage; + /* + * All current "hook run" use-cases require ungrouped child output. + * If this changes, a hook run argument can be added to toggle it. + */ + opt.ungroup = 1; + /* * Having a -- for "run" when providing is * mandatory. diff --git a/commit.c b/commit.c index 16d91b2bfc..7da33dde86 100644 --- a/commit.c +++ b/commit.c @@ -1965,6 +1965,9 @@ int run_commit_hook(int editor_is_used, const char *index_file, strvec_push(&opt.args, arg); va_end(args); + /* All commit hook use-cases require ungrouping child output. */ + opt.ungroup = 1; + opt.invoked_hook = invoked_hook; return run_hooks_opt(the_repository, name, &opt); } diff --git a/hook.c b/hook.c index 5ddd7678d1..00a1e2ad22 100644 --- a/hook.c +++ b/hook.c @@ -153,7 +153,7 @@ int run_hooks_opt(struct repository *r, const char *hook_name, .tr2_label = hook_name, .processes = 1, - .ungroup = 1, + .ungroup = options->ungroup, .get_next_task = pick_next_hook, .start_failure = notify_start_failure, @@ -198,6 +198,9 @@ int run_hooks(struct repository *r, const char *hook_name) { struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + /* All use-cases of this API require ungrouping. */ + opt.ungroup = 1; + return run_hooks_opt(r, hook_name, &opt); } diff --git a/hook.h b/hook.h index 2169d4a6bd..78a1a44690 100644 --- a/hook.h +++ b/hook.h @@ -34,6 +34,11 @@ struct run_hooks_opt */ int *invoked_hook; + /** + * Allow hooks to set run_processes_parallel() 'ungroup' behavior. + */ + unsigned int ungroup:1; + /** * Path to file which should be piped to stdin for each hook. */ -- cgit v1.3 From 53254bfa1b4e91bab2c675d1a6b561026f7b573a Mon Sep 17 00:00:00 2001 From: Emily Shaffer Date: Fri, 26 Dec 2025 14:23:32 +0200 Subject: hooks: allow callers to capture output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some server-side hooks will require capturing output to send over sideband instead of printing directly to stderr. Expose that capability. Signed-off-by: Emily Shaffer Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano --- hook.c | 1 + hook.h | 8 ++++++++ 2 files changed, 9 insertions(+) (limited to 'hook.c') diff --git a/hook.c b/hook.c index 00a1e2ad22..35211e5ed7 100644 --- a/hook.c +++ b/hook.c @@ -158,6 +158,7 @@ int run_hooks_opt(struct repository *r, const char *hook_name, .get_next_task = pick_next_hook, .start_failure = notify_start_failure, .feed_pipe = options->feed_pipe, + .consume_output = options->consume_output, .task_finished = notify_hook_finished, .data = &cb_data, diff --git a/hook.h b/hook.h index 78a1a44690..ae502178b9 100644 --- a/hook.h +++ b/hook.h @@ -80,6 +80,14 @@ struct run_hooks_opt * Only useful when using `run_hooks_opt.feed_pipe`, otherwise ignore it. */ void *feed_pipe_cb_data; + + /* + * Populate this to capture output and prevent it from being printed to + * stderr. This will be passed directly through to + * run_command:run_parallel_processes(). See t/helper/test-run-command.c + * for an example. + */ + consume_output_fn consume_output; }; #define RUN_HOOKS_OPT_INIT { \ -- cgit v1.3