[PATCH v7 0/4] hook: introduce the receive-report hook
From: Karthik Nayak <hidden>
Date: 2026-09-04 21:28:58
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 v7: - Removed report_v2() since it is the same as report() with the new changes. - Used a switch statement instead of an if/else for the enum. - Removed an unnecessary curly brace. - Also rebased on top of latest master (3cb9185f65 (The 22nd batch, 2026-09-02) as there were conflicts. - Link to v6: https://patch.msgid.link/20260903-758-introduce-hook-v6-0-6283b1fb9b1c@gmail.com Changes in v6: - Introduce a new commit which introduces `enum report_status_version`, use that and drop static variables in the codebase. - Reword the commit message and documentation to: - State further why reference-transaction cannot be used. - State the responsibility of the hook owner to undo and reference changes if needed. - Link to v5: https://patch.msgid.link/20260901-758-introduce-hook-v5-0-35cdc6be3cc1@gmail.com Changes in v5: - Rewrote some of the commit messages and documentation. - Renamed the function `generate_response` to `generate_report` to avoid ambiguity. - We now override the cmd's error_strings, this avoids the whole precedence issue with the earlier series. - Also add information about how we can override the unpack status to fail the push and add a corresponding test. - Thanks to Patrick for the review! - Junio: This causes conflict with next ('jt/receive-pack-pluggable-writes') similar to before, please let me know if its better for me to add that dependency. - Link to v4: https://patch.msgid.link/20260826-758-introduce-hook-v4-0-6b14975ad957@gmail.com 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 (4): doc: add proc-receive hook info in 'git-receive-pack.adoc' receive-pack: drop static variables to track report status version receive-pack: move message generation to separate function hook: introduce the receive-report hook Documentation/git-receive-pack.adoc | 17 +++ Documentation/githooks.adoc | 61 ++++++++++ builtin/receive-pack.c | 148 ++++++++++++++++-------- t/meson.build | 1 + t/t5412-receive-report-hook.sh | 224 ++++++++++++++++++++++++++++++++++++ 5 files changed, 403 insertions(+), 48 deletions(-) Range-diff versus v6: 1: 97c946bad0 = 1: efb66eb539 doc: add proc-receive hook info in 'git-receive-pack.adoc' 2: 0f7738eed0 ! 2: d8e4830a6f receive-pack: drop static variables to track report status version @@ builtin/receive-pack.c: enum deny_action { static int deny_deletes; static int deny_non_fast_forwards; static enum deny_action deny_current_branch = DENY_UNCONFIGURED; -@@ builtin/receive-pack.c: static int advertise_push_options; +@@ builtin/receive-pack.c: static int advertise_atomic_push = 1; + static int advertise_push_options; static int advertise_sid; - static int unpack_limit = 100; static off_t max_input_size; -static int report_status; -static int report_status_v2; @@ builtin/receive-pack.c: int cmd_receive_pack(int argc, - if ((commands = read_head_info(&reader, &shallow))) { + if ((commands = read_head_info(&reader, &shallow, &version))) { - const char *unpack_status = NULL; struct string_list push_options = STRING_LIST_INIT_DUP; + struct strbuf unpack_status = STRBUF_INIT; @@ builtin/receive-pack.c: int cmd_receive_pack(int argc, &push_options); - delete_tempfile(&pack_lockfile); + odb_transaction_finalize(transaction); sigchain_push(SIGPIPE, SIG_IGN); - if (report_status_v2) -+ if (version == REPORT_STATUS_V2) - report_v2(commands, unpack_status); ++ ++ switch (version) { ++ case REPORT_STATUS_V2: + report_v2(commands, &unpack_status); - else if (report_status) -+ else if (version == REPORT_STATUS_V0) - report(commands, unpack_status); -+ else ++ break; ++ case REPORT_STATUS_V0: + report(commands, &unpack_status); ++ break; ++ default: + BUG("unknown report status version"); ++ } ++ sigchain_pop(SIGPIPE); run_receive_hook(commands, "post-receive", 1, NULL, &push_options); 3: 8b56349072 ! 3: d9464d9739 receive-pack: move message generation to separate function @@ Commit message knows to provide a more detailed report about how exactly a given reference was updated. + With this, also drop `report_v2()` as both report functions now are + similar in structure with only the `report_status_version` + differentiating them. + In the next commit we're about to add another site that wants to generate these reports. Refactor the logic into a shared function that can easily be reused. @@ builtin/receive-pack.c: static void update_shallow_info(struct command *commands free(ref_status); } --static void report(struct command *commands, const char *unpack_status) +-static void report(struct command *commands, const struct strbuf *unpack_status) +/* + * Generate the response to be sent to the client invoking 'git-receive-pack(1)'. + * For v2 protocol, set `detailed_report` to true, which will also add detailed + * report per reference update. + */ +static void generate_report(struct strbuf *buf, struct command *commands, -+ const char *unpack_status, ++ const struct strbuf *unpack_status, + enum report_status_version version) { struct command *cmd; @@ builtin/receive-pack.c: static void update_shallow_info(struct command *commands - packet_buf_write(&buf, "unpack %s\n", + packet_buf_write(buf, "unpack %s\n", - unpack_status ? unpack_status : "ok"); + unpack_status->len ? unpack_status->buf : "ok"); - for (cmd = commands; cmd; cmd = cmd->next) { - if (!cmd->error_string) - packet_buf_write(&buf, "ok %s\n", @@ builtin/receive-pack.c: static void update_shallow_info(struct command *commands - strbuf_release(&buf); -} - --static void report_v2(struct command *commands, const char *unpack_status) +-static void report_v2(struct command *commands, const struct strbuf *unpack_status) -{ - struct command *cmd; - struct strbuf buf = STRBUF_INIT; - struct ref_push_report *report; - packet_buf_write(&buf, "unpack %s\n", -- unpack_status ? unpack_status : "ok"); +- unpack_status->len ? unpack_status->buf : "ok"); for (cmd = commands; cmd; cmd = cmd->next) { + struct ref_push_report *report; int count = 0; @@ builtin/receive-pack.c: static void update_shallow_info(struct command *commands + packet_buf_flush(buf); +} + -+static void report(struct command *commands, const char *unpack_status) -+{ -+ struct strbuf buf = STRBUF_INIT; -+ -+ generate_report(&buf, commands, unpack_status, REPORT_STATUS_V0); -+ -+ if (use_sideband) -+ send_sideband(1, 1, buf.buf, buf.len, use_sideband); -+ else -+ write_or_die(1, buf.buf, buf.len); -+ strbuf_release(&buf); -+} -+ -+static void report_v2(struct command *commands, const char *unpack_status) ++static void report(struct command *commands, const struct strbuf *unpack_status, ++ enum report_status_version version) +{ + struct strbuf buf = STRBUF_INIT; + -+ generate_report(&buf, commands, unpack_status, REPORT_STATUS_V2); ++ generate_report(&buf, commands, unpack_status, version); if (use_sideband) send_sideband(1, 1, buf.buf, buf.len, use_sideband); +@@ builtin/receive-pack.c: int cmd_receive_pack(int argc, + + switch (version) { + case REPORT_STATUS_V2: +- report_v2(commands, &unpack_status); +- break; + case REPORT_STATUS_V0: +- report(commands, &unpack_status); ++ report(commands, &unpack_status, version); + break; + default: + BUG("unknown report status version"); 4: a3d7576e58 ! 4: 262c8f1708 hook: introduce the receive-report hook @@ builtin/receive-pack.c: static void update_shallow_info(struct command *commands +static void override_cmds_error(struct command *commands, const char *err) +{ -+ for (struct command *cmd = commands; cmd; cmd = cmd->next) { ++ for (struct command *cmd = commands; cmd; cmd = cmd->next) + cmd->error_string = err; -+ } +} + /* * Generate the response to be sent to the client invoking 'git-receive-pack(1)'. * For v2 protocol, set `detailed_report` to true, which will also add detailed -@@ builtin/receive-pack.c: static void report(struct command *commands, const char *unpack_status) +@@ builtin/receive-pack.c: static void report(struct command *commands, const struct strbuf *unpack_status, - generate_report(&buf, commands, unpack_status, REPORT_STATUS_V0); + generate_report(&buf, commands, unpack_status, version); + if (run_receive_report_hook(&buf)) { + strbuf_reset(&buf); + override_cmds_error(commands, "receive-report hook failed"); + generate_report(&buf, commands, unpack_status, false); + } -+ - if (use_sideband) - send_sideband(1, 1, buf.buf, buf.len, use_sideband); - else -@@ builtin/receive-pack.c: static void report_v2(struct command *commands, const char *unpack_status) - - generate_report(&buf, commands, unpack_status, REPORT_STATUS_V2); - -+ if (run_receive_report_hook(&buf)) { -+ strbuf_reset(&buf); -+ override_cmds_error(commands, "receive-report hook failed"); -+ generate_report(&buf, commands, unpack_status, true); -+ } + if (use_sideband) send_sideband(1, 1, buf.buf, buf.len, use_sideband); --- base-commit: 3cb9185f65410273787f74333cc027d2ea5daada change-id: 20260812-758-introduce-hook-5b3af9f1a7e8 Thanks - Karthik