[PATCH v4 0/3] hook: introduce the receive-report hook
- From
Karthik Nayak <karthik.188@gmail.com>
- Date
- Aug 26, 2026, 10:19 UTC
- Message-ID
- <20260826-758-introduce-hook-v4-0-6b14975ad957@gmail.com>
- In-Reply-To
- <20260818-758-introduce-hook-v1-1-8a8d89e65838@gmail.com>
Introduce a new receive-report hook which kicks in after the reference transaction is complete, but before the report is sent to the client. The hook receives the pkt-line encoded report in its stdin and its stdout replaces the report transferred to the user. If the hook exits with a non-zero exit code, all references are marked as rejected.
The first patch, adds missing documentation to 'git-receive-pack.adoc'. The second patch refactors code and the third patch contains the new hook.
--- Changes in v4: - Change the name of the hook to be 'receive-report' to avoid ambiguity. - Link to v3: https://patch.msgid.link/20260824-758-introduce-hook-v3-0-499526f0a062@gmail.com
Changes in v3: - Move out addition of proc-receive hook doc to 'git-receive-pack.adoc' into a new commit. - Add a new commit to move out the response generation in receive-pack to a new function. - Instead of die-ing on non-zero exit code, we modify each reference to indicate that the hook failed. - Instead of correctly listing out the protocol, link to linkgit:gitprotocol-pack[5], as the protocol also differs between v1 and v2. - Link to v2: https://patch.msgid.link/20260821-758-introduce-hook-v2-1-e90e2f7ac2cf@gmail.com
Changes in v2: - Modify the documentation and commit message to be more verbose. - Add documentation to 'git-receive-pack.adoc' - Use 'ret' as the variable name for the return code. - Modify the test to also check for the 'remote:'. - Link to v1: https://patch.msgid.link/20260818-758-introduce-hook-v1-1-8a8d89e65838@gmail.com
To: git@vger.kernel.org CC: ps@pks.im CC: gitster@pobox.com CC: jltobler@gmail.com CC: kristofferhaugsbakk@fastmail.com CC: phillip.wood123@gmail.com
---
Karthik Nayak (3):
doc: add proc-receive hook info in 'git-receive-pack.adoc'
receive-pack: move message generation to separate function
hook: introduce the receive-report hookDocumentation/git-receive-pack.adoc | 15 +++ Documentation/githooks.adoc | 43 ++++++++ builtin/receive-pack.c | 137 ++++++++++++++++-------- t/meson.build | 1 + t/t5412-receive-report-hook.sh | 200 ++++++++++++++++++++++++++++++++++++ 5 files changed, 356 insertions(+), 40 deletions(-)
Range-diff versus v3:
1: b899f31ffa = 1: 30784c0448 doc: add proc-receive hook info in 'git-receive-pack.adoc'
2: 335182cd3d = 2: 55d6a46815 receive-pack: move message generation to separate function
3: 80aa575dab ! 3: 99eeafb537 hook: introduce the report hook for git-receive-pack(1)
@@ Metadata
Author: Karthik Nayak <karthik.188@gmail.com>
## Commit message ##
- hook: introduce the report hook for git-receive-pack(1)
+ hook: introduce the receive-report hook
When running 'git-receive-pack(1)', there is no way for the server to
intercept and modify the status report before it is sent back to the
@@ Commit message
too late, at the point where we have already reported success to the
client.
- Introduce a new 'report' hook. The hook receives the complete pkt-line
- encoded status report on standard input, after all ref updates have
- been applied to the repository by execute_commands() but before the
+ Introduce a new 'receive-report' hook. The hook receives the complete
+ pkt-line encoded status report on standard input, after all ref updates
+ have been applied to the repository by execute_commands() but before the
report is sent to the client. See linkgit:gitprotocol-pack[5] details on
the protocol structure.
@@ Commit message
status if any ref is 'ng'.
- Non-zero exit: the hook's stdout is discarded, receive-pack modifies
- all references to be rejected with a 'report hook failed' error.
+ all references to be rejected with a 'receive-report hook failed'
+ error.
In both cases, any output the hook writes to standard error is
forwarded to the client over the sideband channel and appears as
@@ Documentation/git-receive-pack.adoc: requests. It handles refs whose names match
`receive.procReceiveRefs` and executes the actual ref updates. See
linkgit:githooks[5] for the full protocol description.
-+REPORT HOOK
-+-----------
++RECEIVE-REPORT HOOK
++-------------------
+This hook is invoked by 'git-receive-pack' after all the ref updates
+have been applied but before the report is sent to the client. The hook
+receives the complete report in pkt-line format on stdin and its stdout
@@ Documentation/githooks.adoc: The exit status of the hook is ignored for any stat
status will cause the transaction to be aborted. The hook will not be
called with "aborted" state in that case.
-+report
-+~~~~~~
++receive-report
++~~~~~~~~~~~~~~
+
+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to
+`git push` and updates references in its repository. It executes on
@@ Documentation/githooks.adoc: The exit status of the hook is ignored for any stat
+
+* To abort the entire push unconditionally, exit with a non-zero
+ status. In this case the hook's stdout is discarded, `receive-pack`
-+ modifies all references to be rejected with a 'report hook failed'
-+ error.
++ modifies all references to be rejected with a 'receive-report hook
++ failed' error.
+
+Any output written to standard error is forwarded to the client over
+the sideband channel and will appear as `remote:` lines on clients
@@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)
return code;
}
-+static int run_report_hook(struct strbuf *report)
++static int run_receive_report_hook(struct strbuf *report)
+{
+ struct child_process proc = CHILD_PROCESS_INIT;
+ struct async sideband_async;
@@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)
+ const char *hook_path;
+ int ret;
+
-+ hook_path = find_hook(the_repository, "report");
++ hook_path = find_hook(the_repository, "receive-report");
+ if (!hook_path)
+ return 0;
+
+ strvec_push(&proc.args, hook_path);
-+ proc.trace2_hook_name = "report";
++ proc.trace2_hook_name = "receive-report";
+
+ prepare_sideband_async(&sideband_async, &saved_stderr,
+ &sideband_async_started);
@@ builtin/receive-pack.c: static void report(struct command *commands, const char
- generate_response(&buf, commands, unpack_status, false);
+ generate_response(&buf, commands, unpack_status, false, NULL);
+
-+ if (run_report_hook(&buf)) {
++ if (run_receive_report_hook(&buf)) {
+ strbuf_reset(&buf);
+ generate_response(&buf, commands, unpack_status, false,
-+ "report hook failed");
++ "receive-report hook failed");
+ }
if (use_sideband)
@@ builtin/receive-pack.c: static void report_v2(struct command *commands, const ch
- generate_response(&buf, commands, unpack_status, true);
+ generate_response(&buf, commands, unpack_status, true, NULL);
+
-+ if (run_report_hook(&buf)) {
++ if (run_receive_report_hook(&buf)) {
+ strbuf_reset(&buf);
+ generate_response(&buf, commands, unpack_status, true,
-+ "report hook failed");
++ "receive-report hook failed");
+ }
if (use_sideband)
@@ t/meson.build: integration_tests = [
't5409-colorize-remote-messages.sh',
't5410-receive-pack.sh',
't5411-proc-receive-hook.sh',
-+ 't5412-report-hook.sh',
++ 't5412-receive-report-hook.sh',
't5500-fetch-pack.sh',
't5501-fetch-push-alternates.sh',
't5502-quickfetch.sh',
- ## t/t5412-report-hook.sh (new) ##
+ ## t/t5412-receive-report-hook.sh (new) ##
@@
+#!/bin/sh
+
-+test_description='test report hook'
++test_description='test receive-report hook'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
@@ t/t5412-report-hook.sh (new)
+ test_when_finished "git -C workbench remote remove origin" &&
+ git init --bare upstream &&
+
-+ test_hook -C upstream --setup report <<-\EOF &&
++ test_hook -C upstream --setup receive-report <<-\EOF &&
+ cat
+ EOF
+
@@ t/t5412-report-hook.sh (new)
+ git -C workbench remote add origin ../upstream &&
+ git -C workbench push origin $A:refs/heads/main &&
+
-+ test_hook -C upstream --setup report <<-\EOF &&
++ test_hook -C upstream --setup receive-report <<-\EOF &&
+ exit 1
+ EOF
+
@@ t/t5412-report-hook.sh (new)
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-\EOF &&
+ To ../upstream
-+ ! [remote rejected] <COMMIT-B> -> main (report hook failed)
++ ! [remote rejected] <COMMIT-B> -> main (receive-report hook failed)
+ EOF
+ test_cmp expect actual
+'
@@ t/t5412-report-hook.sh (new)
+ test_when_finished "git -C workbench remote remove origin" &&
+
+ git init --bare upstream &&
-+ test_hook -C upstream --setup report <<-EOF &&
++ test_hook -C upstream --setup receive-report <<-EOF &&
+ tee raw
+ EOF
+
@@ t/t5412-report-hook.sh (new)
+ git -C workbench remote add origin ../upstream &&
+ git -C workbench push origin $A:refs/heads/main &&
+
-+ test_hook -C upstream --setup report <<-\EOF &&
++ test_hook -C upstream --setup receive-report <<-\EOF &&
+ test-tool pkt-line unpack |
+ sed "s/^ok /ng /" |
+ test-tool pkt-line pack
@@ t/t5412-report-hook.sh (new)
+ git -C workbench remote add origin ../upstream &&
+ git -C workbench push origin $A:refs/heads/main &&
+
-+ test_hook -C upstream --setup report <<-\EOF &&
++ test_hook -C upstream --setup receive-report <<-\EOF &&
+ echo "push rejected: service X is down" >&2
+ test-tool pkt-line unpack |
+ sed "s/^ok \(.*\)/ng \1 service-x-is-down/" |
@@ t/t5412-report-hook.sh (new)
+ git -C workbench remote add origin ../upstream &&
+ git -C workbench push origin $A:refs/heads/main &&
+
-+ test_hook -C upstream --setup report <<-\EOF &&
++ test_hook -C upstream --setup receive-report <<-\EOF &&
+ echo "push rejected: service X is down" >&2
+ tee raw
+ EOF
@@ t/t5412-report-hook.sh (new)
+ git -C workbench remote add origin ../upstream &&
+ git -C workbench push origin $A:refs/heads/main &&
+
-+ test_hook -C upstream --setup report <<-\EOF &&
++ test_hook -C upstream --setup receive-report <<-\EOF &&
+ echo "hook-stderr-message" >&2
+ exit 1
+ EOF--- base-commit: 11c6700f10234578d10523faf35656ca491425c9 change-id: 20260812-758-introduce-hook-5b3af9f1a7e8
Thanks - Karthik