Thread (61 messages) flat view 61 messages, 5 authors, 1d ago
WARM1d

Revision v7 of 4 in this series.

Revisions (4)
  1. v4 [diff vs current]
  2. v5 [diff vs current]
  3. v6 [diff vs current]
  4. v7 current

[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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help