Merge branch 'ab/hook-api-with-stdin'

Extend the run-hooks API to allow feeding data from the standard
input when running the hook script(s).

* ab/hook-api-with-stdin:
  hook: support a --to-stdin=<path> option
  sequencer: use the new hook API for the simpler "post-rewrite" call
  hook API: support passing stdin to hooks, convert am's 'post-rewrite'
  run-command: allow stdin for run_processes_parallel
  run-command.c: remove dead assignment in while-loop
This commit is contained in:
Junio C Hamano 2023-02-22 14:55:45 -08:00
commit 5048df67b2
8 changed files with 54 additions and 36 deletions

View File

@ -8,7 +8,7 @@ git-hook - Run git hooks
SYNOPSIS SYNOPSIS
-------- --------
[verse] [verse]
'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>] 'git hook' run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]
DESCRIPTION DESCRIPTION
----------- -----------
@ -31,6 +31,11 @@ linkgit:githooks[5] for arguments hooks might expect (if any).
OPTIONS OPTIONS
------- -------
--to-stdin::
For "run"; Specify a file which will be streamed into the
hook's stdin. The hook will receive the entire file from
beginning to EOF.
--ignore-missing:: --ignore-missing::
Ignore any missing hook by quietly returning zero. Used for Ignore any missing hook by quietly returning zero. Used for
tools that want to do a blind one-shot run of a hook that may tools that want to do a blind one-shot run of a hook that may

View File

@ -495,24 +495,12 @@ static int run_applypatch_msg_hook(struct am_state *state)
*/ */
static int run_post_rewrite_hook(const struct am_state *state) static int run_post_rewrite_hook(const struct am_state *state)
{ {
struct child_process cp = CHILD_PROCESS_INIT; struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
const char *hook = find_hook("post-rewrite");
int ret;
if (!hook) strvec_push(&opt.args, "rebase");
return 0; opt.path_to_stdin = am_path(state, "rewritten");
strvec_push(&cp.args, hook); return run_hooks_opt("post-rewrite", &opt);
strvec_push(&cp.args, "rebase");
cp.in = xopen(am_path(state, "rewritten"), O_RDONLY);
cp.stdout_to_stderr = 1;
cp.trace2_hook_name = "post-rewrite";
ret = run_command(&cp);
close(cp.in);
return ret;
} }
/** /**

View File

@ -7,7 +7,7 @@
#include "strvec.h" #include "strvec.h"
#define BUILTIN_HOOK_RUN_USAGE \ #define BUILTIN_HOOK_RUN_USAGE \
N_("git hook run [--ignore-missing] <hook-name> [-- <hook-args>]") N_("git hook run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]")
static const char * const builtin_hook_usage[] = { static const char * const builtin_hook_usage[] = {
BUILTIN_HOOK_RUN_USAGE, BUILTIN_HOOK_RUN_USAGE,
@ -28,6 +28,8 @@ static int run(int argc, const char **argv, const char *prefix)
struct option run_options[] = { struct option run_options[] = {
OPT_BOOL(0, "ignore-missing", &ignore_missing, OPT_BOOL(0, "ignore-missing", &ignore_missing,
N_("silently ignore missing requested <hook-name>")), N_("silently ignore missing requested <hook-name>")),
OPT_STRING(0, "to-stdin", &opt.path_to_stdin, N_("path"),
N_("file to read into hooks' stdin")),
OPT_END(), OPT_END(),
}; };
int ret; int ret;

5
hook.c
View File

@ -55,6 +55,11 @@ static int pick_next_hook(struct child_process *cp,
cp->no_stdin = 1; cp->no_stdin = 1;
strvec_pushv(&cp->env, hook_cb->options->env.v); strvec_pushv(&cp->env, hook_cb->options->env.v);
/* 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);
}
cp->stdout_to_stderr = 1; cp->stdout_to_stderr = 1;
cp->trace2_hook_name = hook_cb->hook_name; cp->trace2_hook_name = hook_cb->hook_name;
cp->dir = hook_cb->options->dir; cp->dir = hook_cb->options->dir;

5
hook.h
View File

@ -30,6 +30,11 @@ struct run_hooks_opt
* was invoked. * was invoked.
*/ */
int *invoked_hook; int *invoked_hook;
/**
* Path to file which should be piped to stdin for each hook.
*/
const char *path_to_stdin;
}; };
#define RUN_HOOKS_OPT_INIT { \ #define RUN_HOOKS_OPT_INIT { \

View File

@ -1586,6 +1586,14 @@ static int pp_start_one(struct parallel_processes *pp,
if (i == opts->processes) if (i == opts->processes)
BUG("bookkeeping is hard"); BUG("bookkeeping is hard");
/*
* By default, do not inherit stdin from the parent process - otherwise,
* all children would share stdin! Users may overwrite this to provide
* something to the child's stdin by having their 'get_next_task'
* callback assign 0 to .no_stdin and an appropriate integer to .in.
*/
pp->children[i].process.no_stdin = 1;
code = opts->get_next_task(&pp->children[i].process, code = opts->get_next_task(&pp->children[i].process,
opts->ungroup ? NULL : &pp->children[i].err, opts->ungroup ? NULL : &pp->children[i].err,
opts->data, opts->data,
@ -1601,7 +1609,6 @@ static int pp_start_one(struct parallel_processes *pp,
pp->children[i].process.err = -1; pp->children[i].process.err = -1;
pp->children[i].process.stdout_to_stderr = 1; pp->children[i].process.stdout_to_stderr = 1;
} }
pp->children[i].process.no_stdin = 1;
if (start_command(&pp->children[i].process)) { if (start_command(&pp->children[i].process)) {
if (opts->start_failure) if (opts->start_failure)
@ -1632,9 +1639,7 @@ static void pp_buffer_stderr(struct parallel_processes *pp,
const struct run_process_parallel_opts *opts, const struct run_process_parallel_opts *opts,
int output_timeout) int output_timeout)
{ {
int i; while (poll(pp->pfd, opts->processes, output_timeout) < 0) {
while ((i = poll(pp->pfd, opts->processes, output_timeout) < 0)) {
if (errno == EINTR) if (errno == EINTR)
continue; continue;
pp_cleanup(pp, opts); pp_cleanup(pp, opts);

View File

@ -4844,8 +4844,7 @@ cleanup_head_ref:
if (!stat(rebase_path_rewritten_list(), &st) && if (!stat(rebase_path_rewritten_list(), &st) &&
st.st_size > 0) { st.st_size > 0) {
struct child_process child = CHILD_PROCESS_INIT; struct child_process child = CHILD_PROCESS_INIT;
const char *post_rewrite_hook = struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
find_hook("post-rewrite");
child.in = open(rebase_path_rewritten_list(), O_RDONLY); child.in = open(rebase_path_rewritten_list(), O_RDONLY);
child.git_cmd = 1; child.git_cmd = 1;
@ -4855,18 +4854,9 @@ cleanup_head_ref:
/* we don't care if this copying failed */ /* we don't care if this copying failed */
run_command(&child); run_command(&child);
if (post_rewrite_hook) { hook_opt.path_to_stdin = rebase_path_rewritten_list();
struct child_process hook = CHILD_PROCESS_INIT; strvec_push(&hook_opt.args, "rebase");
run_hooks_opt("post-rewrite", &hook_opt);
hook.in = open(rebase_path_rewritten_list(),
O_RDONLY);
hook.stdout_to_stderr = 1;
hook.trace2_hook_name = "post-rewrite";
strvec_push(&hook.args, post_rewrite_hook);
strvec_push(&hook.args, "rebase");
/* we don't care if this hook failed */
run_command(&hook);
}
} }
apply_autostash(rebase_path_autostash()); apply_autostash(rebase_path_autostash());

View File

@ -177,4 +177,22 @@ test_expect_success 'git hook run a hook with a bad shebang' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success 'stdin to hooks' '
write_script .git/hooks/test-hook <<-\EOF &&
echo BEGIN stdin
cat
echo END stdin
EOF
cat >expect <<-EOF &&
BEGIN stdin
hello
END stdin
EOF
echo hello >input &&
git hook run --to-stdin=input test-hook 2>actual &&
test_cmp expect actual
'
test_done test_done