git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v3 16/17] receive-pack: convert receive hooks to hook.h

From
Emily Shaffer <emilyshaffer@google.com>
Date
Dec 22, 2020, 00:04 UTC
Message-ID
<20201222000435.1529768-17-emilyshaffer@google.com>
In-Reply-To
<20201222000435.1529768-1-emilyshaffer@google.com>

By using the hook.h library to run receive hooks, they can be specified in the config as well as in the hookdir.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
---
 Documentation/githooks.txt |   4 +
 builtin/receive-pack.c     | 197 +++++++++++++++++--------------------
 2 files changed, 94 insertions(+), 107 deletions(-)
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 1412cd5846..c450f7a27e 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -314,6 +314,8 @@ will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
 See the section on "Quarantine Environment" in
 linkgit:git-receive-pack[1] for some caveats.
 
+Hooks executed during 'pre-receive' will not be parallelized.
+
 [[update]]
 update
 ~~~~~~
@@ -466,6 +468,8 @@ environment variables will not be set. If the client selects
 to use push options, but doesn't transmit any, the count variable
 will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
 
+Hooks executed during 'post-receive' are run in parallel by default.
+
 [[post-update]]
 post-update
 ~~~~~~~~~~~
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 5c23d5ed86..348d5b0a9b 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -748,7 +748,7 @@ static int check_cert_push_options(const struct string_list *push_options)
 	return retval;
 }
 
-static void prepare_push_cert_sha1(struct child_process *proc)
+static void prepare_push_cert_sha1(struct run_hooks_opt *opt)
 {
 	static int already_done;
 
@@ -772,110 +772,42 @@ static void prepare_push_cert_sha1(struct child_process *proc)
 		nonce_status = check_nonce(push_cert.buf, bogs);
 	}
 	if (!is_null_oid(&push_cert_oid)) {
-		strvec_pushf(&proc->env_array, "GIT_PUSH_CERT=%s",
+		strvec_pushf(&opt->env, "GIT_PUSH_CERT=%s",
 			     oid_to_hex(&push_cert_oid));
-		strvec_pushf(&proc->env_array, "GIT_PUSH_CERT_SIGNER=%s",
+		strvec_pushf(&opt->env, "GIT_PUSH_CERT_SIGNER=%s",
 			     sigcheck.signer ? sigcheck.signer : "");
-		strvec_pushf(&proc->env_array, "GIT_PUSH_CERT_KEY=%s",
+		strvec_pushf(&opt->env, "GIT_PUSH_CERT_KEY=%s",
 			     sigcheck.key ? sigcheck.key : "");
-		strvec_pushf(&proc->env_array, "GIT_PUSH_CERT_STATUS=%c",
+		strvec_pushf(&opt->env, "GIT_PUSH_CERT_STATUS=%c",
 			     sigcheck.result);
 		if (push_cert_nonce) {
-			strvec_pushf(&proc->env_array,
+			strvec_pushf(&opt->env,
 				     "GIT_PUSH_CERT_NONCE=%s",
 				     push_cert_nonce);
-			strvec_pushf(&proc->env_array,
+			strvec_pushf(&opt->env,
 				     "GIT_PUSH_CERT_NONCE_STATUS=%s",
 				     nonce_status);
 			if (nonce_status == NONCE_SLOP)
-				strvec_pushf(&proc->env_array,
+				strvec_pushf(&opt->env,
 					     "GIT_PUSH_CERT_NONCE_SLOP=%ld",
 					     nonce_stamp_slop);
 		}
 	}
 }
 
+struct receive_hook_feed_context {
+	struct command *cmd;
+	int skip_broken;
+};
+
 struct receive_hook_feed_state {
 	struct command *cmd;
 	struct ref_push_report *report;
 	int skip_broken;
 	struct strbuf buf;
-	const struct string_list *push_options;
 };
 
-typedef int (*feed_fn)(void *, const char **, size_t *);
-static int run_and_feed_hook(const char *hook_name, feed_fn feed,
-			     struct receive_hook_feed_state *feed_state)
-{
-	struct child_process proc = CHILD_PROCESS_INIT;
-	struct async muxer;
-	const char *argv[2];
-	int code;
-
-	argv[0] = find_hook(hook_name);
-	if (!argv[0])
-		return 0;
-
-	argv[1] = NULL;
-
-	proc.argv = argv;
-	proc.in = -1;
-	proc.stdout_to_stderr = 1;
-	proc.trace2_hook_name = hook_name;
-
-	if (feed_state->push_options) {
-		int i;
-		for (i = 0; i < feed_state->push_options->nr; i++)
-			strvec_pushf(&proc.env_array,
-				     "GIT_PUSH_OPTION_%d=%s", i,
-				     feed_state->push_options->items[i].string);
-		strvec_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d",
-			     feed_state->push_options->nr);
-	} else
-		strvec_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");
-
-	if (tmp_objdir)
-		strvec_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir));
-
-	if (use_sideband) {
-		memset(&muxer, 0, sizeof(muxer));
-		muxer.proc = copy_to_sideband;
-		muxer.in = -1;
-		code = start_async(&muxer);
-		if (code)
-			return code;
-		proc.err = muxer.in;
-	}
-
-	prepare_push_cert_sha1(&proc);
-
-	code = start_command(&proc);
-	if (code) {
-		if (use_sideband)
-			finish_async(&muxer);
-		return code;
-	}
-
-	sigchain_push(SIGPIPE, SIG_IGN);
-
-	while (1) {
-		const char *buf;
-		size_t n;
-		if (feed(feed_state, &buf, &n))
-			break;
-		if (write_in_full(proc.in, buf, n) < 0)
-			break;
-	}
-	close(proc.in);
-	if (use_sideband)
-		finish_async(&muxer);
-
-	sigchain_pop(SIGPIPE);
-
-	return finish_command(&proc);
-}
-
-static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
+static int feed_receive_hook(void *state_)
 {
 	struct receive_hook_feed_state *state = state_;
 	struct command *cmd = state->cmd;
@@ -884,9 +816,7 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
 	       state->skip_broken && (cmd->error_string || cmd->did_not_exist))
 		cmd = cmd->next;
 	if (!cmd)
-		return -1; /* EOF */
-	if (!bufp)
-		return 0; /* OK, can feed something. */
+		return 1; /* EOF - close the pipe*/
 	strbuf_reset(&state->buf);
 	if (!state->report)
 		state->report = cmd->report;
@@ -910,32 +840,36 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
 			    cmd->ref_name);
 		state->cmd = cmd->next;
 	}
-	if (bufp) {
-		*bufp = state->buf.buf;
-		*sizep = state->buf.len;
-	}
 	return 0;
 }
 
-static int run_receive_hook(struct command *commands,
-			    const char *hook_name,
-			    int skip_broken,
-			    const struct string_list *push_options)
+static int feed_receive_hook_cb(struct strbuf *pipe, void *pp_cb, void *pp_task_cb)
 {
-	struct receive_hook_feed_state state;
-	int status;
-
-	strbuf_init(&state.buf, 0);
-	state.cmd = commands;
-	state.skip_broken = skip_broken;
-	state.report = NULL;
-	if (feed_receive_hook(&state, NULL, NULL))
-		return 0;
-	state.cmd = commands;
-	state.push_options = push_options;
-	status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
-	strbuf_release(&state.buf);
-	return status;
+	struct hook *hook = pp_task_cb;
+	struct receive_hook_feed_state *feed_state = hook->feed_pipe_cb_data;
+	int rc;
+
+	/* first-time setup */
+	if (!feed_state) {
+		struct hook_cb_data *hook_cb = pp_cb;
+		struct run_hooks_opt *opt = hook_cb->options;
+		struct receive_hook_feed_context *ctx = opt->feed_pipe_ctx;
+		if (!ctx)
+			BUG("run_hooks_opt.feed_pipe_ctx required for receive hook");
+
+		feed_state = xmalloc(sizeof(struct receive_hook_feed_state));
+		strbuf_init(&feed_state->buf, 0);
+		feed_state->cmd = ctx->cmd;
+		feed_state->skip_broken = ctx->skip_broken;
+		feed_state->report = NULL;
+
+		hook->feed_pipe_cb_data = feed_state;
+	}
+
+	rc = feed_receive_hook(feed_state);
+	if (!rc)
+		strbuf_addbuf(pipe, &feed_state->buf);
+	return rc;
 }
 
 static void hook_output_to_sideband(struct strbuf *output, void *cb_data)
@@ -971,6 +905,55 @@ static void hook_output_to_sideband(struct strbuf *output, void *cb_data)
 	send_sideband(1, 2, output->buf, output->len, use_sideband);
 }
 
+static int run_receive_hook(struct command *commands,
+			    const char *hook_name,
+			    int skip_broken,
+			    const struct string_list *push_options)
+{
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;
+	struct receive_hook_feed_context ctx;
+	int rc;
+	struct command *iter = commands;
+
+	/* if there are no valid commands, don't invoke the hook at all. */
+	while (iter && skip_broken && (iter->error_string || iter->did_not_exist))
+		iter = iter->next;
+	if (!iter)
+		return 0;
+
+	/* pre-receive hooks should run in series as the hook updates refs */
+	if (!strcmp(hook_name, "pre-receive"))
+		opt.jobs = 1;
+
+	if (push_options) {
+		int i;
+		for (i = 0; i < push_options->nr; i++)
+			strvec_pushf(&opt.env, "GIT_PUSH_OPTION_%d=%s", i,
+				     push_options->items[i].string);
+		strvec_pushf(&opt.env, "GIT_PUSH_OPTION_COUNT=%d", push_options->nr);
+	} else
+		strvec_push(&opt.env, "GIT_PUSH_OPTION_COUNT");
+
+	if (tmp_objdir)
+		strvec_pushv(&opt.env, tmp_objdir_env(tmp_objdir));
+
+	prepare_push_cert_sha1(&opt);
+
+	/* set up sideband printer */
+	if (use_sideband)
+		opt.consume_sideband = hook_output_to_sideband;
+
+	/* set up stdin callback */
+	ctx.cmd = commands;
+	ctx.skip_broken = skip_broken;
+	opt.feed_pipe = feed_receive_hook_cb;
+	opt.feed_pipe_ctx = &ctx;
+
+	rc = run_hooks(hook_name, &opt);
+	run_hooks_opt_clear(&opt);
+	return rc;
+}
+
 static int run_update_hook(struct command *cmd)
 {
 	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;
-- 
2.28.0.rc0.142.g3c755180ce-goog
Previous: Emily ShafferNext: Emily Shaffer
Message 44 of 53 in “commit: use config-based hooks (config-based hooks part II)”
  1. commit: use config-based hooks (config-based hooks part II)Emily Shaffer, Oct 14, 2020
  2. Junio C HamanoOct 16, 2020
  3. 00/17 use config-based hooks (config-based hooks partEmily Shaffer, Dec 5, 2020
  4. 01/17 commit: use config-based hooksEmily Shaffer, Dec 5, 2020
  5. 03/17 merge: use config-based hooks for post-merge hookEmily Shaffer, Dec 5, 2020
  6. 02/17 am: convert applypatch hooks to use configEmily Shaffer, Dec 5, 2020
  7. 05/17 rebase: teach pre-rebase to use hook.hEmily Shaffer, Dec 5, 2020
  8. 04/17 gc: use hook library for pre-auto-gc hookEmily Shaffer, Dec 5, 2020
  9. 15/17 post-update: use hook.h libraryEmily Shaffer, Dec 5, 2020
  10. 06/17 read-cache: convert post-index-change hook to use configEmily Shaffer, Dec 5, 2020
  11. 07/17 receive-pack: convert push-to-checkout hook to hook.hEmily Shaffer, Dec 5, 2020
  12. 14/17 proc-receive: acquire hook list from hook.hEmily Shaffer, Dec 5, 2020
  13. 09/17 hooks: convert 'post-checkout' hook to hook libraryEmily Shaffer, Dec 5, 2020
  14. 12/17 reference-transaction: look for hooks in configEmily Shaffer, Dec 5, 2020
  15. 11/17 transport: convert pre-push hook to use configEmily Shaffer, Dec 5, 2020
  16. 08/17 git-p4: use 'git hook' to run hooksEmily Shaffer, Dec 5, 2020
  17. Josh SteadmonDec 16, 2020
  18. Emily ShafferDec 16, 2020
  19. 10/17 hook: convert 'post-rewrite' hook to configEmily Shaffer, Dec 5, 2020
  20. Josh SteadmonDec 8, 2020
  21. Emily ShafferDec 15, 2020
  22. 13/17 receive-pack: convert 'update' hook to hook.hEmily Shaffer, Dec 5, 2020
  23. 16/17 receive-pack: convert receive hooks to hook.hEmily Shaffer, Dec 5, 2020
  24. 17/17 run-command: stop thinking about hooksEmily Shaffer, Dec 5, 2020
  25. 00/17 use config-based hooks (config-based hooks part II)Emily Shaffer, Dec 22, 2020
  26. 04/17 gc: use hook library for pre-auto-gc hookEmily Shaffer, Dec 22, 2020
  27. 01/17 commit: use config-based hooksEmily Shaffer, Dec 22, 2020
  28. Junio C HamanoFeb 1, 2021
  29. Emily ShafferMar 10, 2021
  30. Junio C HamanoMar 10, 2021
  31. Junio C HamanoFeb 1, 2021
  32. Emily ShafferMar 10, 2021
  33. 03/17 merge: use config-based hooks for post-merge hookEmily Shaffer, Dec 22, 2020
  34. 06/17 read-cache: convert post-index-change hook to use configEmily Shaffer, Dec 22, 2020
  35. 02/17 am: convert applypatch hooks to use configEmily Shaffer, Dec 22, 2020
  36. Junio C HamanoFeb 1, 2021
  37. 05/17 rebase: teach pre-rebase to use hook.hEmily Shaffer, Dec 22, 2020
  38. 07/17 receive-pack: convert push-to-checkout hook to hook.hEmily Shaffer, Dec 22, 2020
  39. 09/17 hooks: convert 'post-checkout' hook to hook libraryEmily Shaffer, Dec 22, 2020
  40. 08/17 git-p4: use 'git hook' to run hooksEmily Shaffer, Dec 22, 2020
  41. 13/17 receive-pack: convert 'update' hook to hook.hEmily Shaffer, Dec 22, 2020
  42. 14/17 proc-receive: acquire hook list from hook.hEmily Shaffer, Dec 22, 2020
  43. 10/17 hook: convert 'post-rewrite' hook to configEmily Shaffer, Dec 22, 2020
  44. 16/17 receive-pack: convert receive hooks to hook.hEmily Shaffer, Dec 22, 2020
  45. 12/17 reference-transaction: look for hooks in configEmily Shaffer, Dec 22, 2020
  46. 17/17 run-command: stop thinking about hooksEmily Shaffer, Dec 22, 2020
  47. 15/17 post-update: use hook.h libraryEmily Shaffer, Dec 22, 2020
  48. 11/17 transport: convert pre-push hook to use configEmily Shaffer, Dec 22, 2020
  49. Emily ShafferDec 28, 2020
  50. 18/17 doc: make git-hook.txt point of truthEmily Shaffer, Dec 28, 2020
  51. Emily ShafferDec 28, 2020
  52. Josh SteadmonFeb 18, 2021
  53. Josh SteadmonDec 16, 2020

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.