Subject: [PATCH v14 0/7] New proc-receive hook for centralized workflow

From: Jiang Xin <[email-masked]>
Date: Tue, 5 May 2020 10:41:24 -0400

From: Jiang Xin <[email-masked]>

This topic introduces a new hook named "proc-receive" for
"receive-pack".  We can use this hook to implement a Gerrit-like
centralized workflow.

## Changes since v13

1. Patch 3/7 introduced a new capability "report-status-v2" for git-push 

   Add new capability "report-status-v2" and new report protocol which is
   not backward compatible for report of git-push.
   
   If a user pushes to a pseudo-reference "refs/for/master/topic", and
   "receive-pack" creates two new references "refs/changes/23/123/1" and
   "refs/changes/24/124/1", for client without the knowledge of
   "report-status-v2", "receive-pack" will only send "ok/ng" directives in
   the report, such as:
   
       ok ref/for/master/topic
   
   But for client which has the knowledge of "report-status-v2",
   "receive-pack" will use "option" directives to report more attributes
   for the reference given by the above "ok/ng" directive.
   
       ok refs/for/master/topic
       option refname refs/changes/23/123/1
       option new-oid <new-oid>
       ok refs/for/master/topic
       option refname refs/changes/24/124/1
       option new-oid <new-oid>
   
   The client will report two new created references to the end user.

2. Patch 2/7: the "proc-receive" reports status using the same way like
   "report-status-v2" to "receive-pack".

## Range-diff v13...v14

1:  dc7f1d044d ! 1:  9a19919552 transport: not report a non-head push as a branch
    @@ t/t5411-proc-receive-hook.sh (new)
     +	git -C upstream.git config http.receivepack true &&
     +	upstream="$HTTPD_DOCUMENT_ROOT_PATH/upstream.git" &&
     +	mv upstream.git "$upstream" &&
    ++	set_askpass user@host pass@host &&
     +	git -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git
     +'
     +
    @@ t/t5411/test-0000-standard-git-push.sh (new)
     +# Refs of upstream : master(A)  
     +# Refs of workbench: master(A)  tags/v123
     +# git-push         : master(B)             next(A)
    -+test_expect_success "non-force git-push ($PROTOCOL)" '
    ++test_expect_success "git-push ($PROTOCOL)" '
     +	git -C workbench push origin \
     +		$B:refs/heads/master \
     +		HEAD:refs/heads/next \
    @@ t/t5411/test-0000-standard-git-push.sh (new)
     +# Refs of upstream : master(B)  next(A)
     +# Refs of workbench: master(A)           tags/v123
     +# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)
    -+test_expect_success "force git-push ($PROTOCOL)" '
    ++test_expect_success "git-push -f ($PROTOCOL)" '
     +	git -C workbench push -f origin \
     +		refs/tags/v123 \
     +		:refs/heads/next \
    @@ t/t5411/test-0001-standard-git-push--porcelain.sh (new)
     +# Refs of upstream : master(A)  
     +# Refs of workbench: master(A)  tags/v123
     +# git-push         : master(B)             next(A)
    -+test_expect_success "non-force git-push (--porcelain, $PROTOCOL)" '
    ++test_expect_success "git-push ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain origin \
     +		$B:refs/heads/master \
     +		HEAD:refs/heads/next \
    @@ t/t5411/test-0001-standard-git-push--porcelain.sh (new)
     +# Refs of upstream : master(B)  next(A)
     +# Refs of workbench: master(A)           tags/v123
     +# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)
    -+test_expect_success "force git-push (--porcelain, $PROTOCOL)" '
    ++test_expect_success "git-push -f ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain -f origin \
     +		refs/tags/v123 \
     +		:refs/heads/next \
    @@ t/t5411/test-0001-standard-git-push--porcelain.sh (new)
     +		git update-ref -d refs/heads/a/b/c
     +	)
     +'
    -+
     
      ## t/t5411/test-0002-pre-receive-declined.sh (new) ##
     @@
    -+test_expect_success "setup pre-receive hook (declined, $PROTOCOL)" '
    ++test_expect_success "setup pre-receive hook ($PROTOCOL)" '
     +	mv "$upstream/hooks/pre-receive" "$upstream/hooks/pre-receive.ok" &&
     +	write_script "$upstream/hooks/pre-receive" <<-EOF
     +	exit 1
    @@ t/t5411/test-0002-pre-receive-declined.sh (new)
     
      ## t/t5411/test-0003-pre-receive-declined--porcelain.sh (new) ##
     @@
    -+test_expect_success "setup pre-receive hook (declined, $PROTOCOL)" '
    ++test_expect_success "setup pre-receive hook ($PROTOCOL)" '
     +	mv "$upstream/hooks/pre-receive" "$upstream/hooks/pre-receive.ok" &&
     +	write_script "$upstream/hooks/pre-receive" <<-EOF
     +	exit 1
    @@ t/t5411/test-0003-pre-receive-declined--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git-push         : master(B)             next(A)
    -+test_expect_success "git-push is declined (--porcelain, $PROTOCOL)" '
    ++test_expect_success "git-push is declined ($PROTOCOL/porcelain)" '
     +	test_must_fail git -C workbench push --porcelain origin \
     +		$B:refs/heads/master \
     +		HEAD:refs/heads/next \
2:  673f44599f < -:  ---------- connect: export parse_feature_value()
3:  43647b379a ! 2:  f1a3ca9cda receive-pack: add new proc-receive hook
    @@ Commit message
             # NO, I reject it.
             H: PKT-LINE(ng <ref> <reason>)
             # Fall through, let 'receive-pack' to execute it.
    -        H: PKT-LINE(ft <ref>)
    +        H: PKT-LINE(ok <ref>)
    +        H: PKT-LINE(option fall-through)
             # OK, but has an alternate reference.  The alternate reference name
    -        # is given in the third parameter, and other status can be given in
    -        # key-value pairs.
    -        H: PKT-LINE(alt <ref> <alt-ref> old-oid=<old-oid> new-oid=<new-oid>
    -                    forced-update)
    +        # and other status can be given in options
    +        H: PKT-LINE(ok <ref>)
    +        H: PKT-LINE(option refname <refname>)
    +        H: PKT-LINE(option old-oid <old-oid>)
    +        H: PKT-LINE(option new-oid <new-oid>)
    +        H: PKT-LINE(option forced-update)
             H: ... ...
             H: flush-pkt
     
    @@ Commit message
         create/update different reference.  For example, a command for a pseudo
         reference "refs/for/master/topic" may create/update different reference
         such as "refs/pull/123/head".  The alternate reference name and other
    -    status are given in key-value pairs as extended status of the report
    -    line.
    +    status are given in option lines.
     
         The list of commands returned from "proc-receive" will replace the
         relevant commands that are sent from user to "receive-pack", and
    @@ Commit message
         other routines.  Finally, the result of the execution of these commands
         will be reported to end user.
     
    -    The reporting function from "receive-pack" to "send-pack" is extended
    -    using a backward compatible way by adding key-value pairs after an null
    -    character, like:
    -
    -        # OK, run this command successfully with optional extended-status.
    -        ok <reference>\0ref=refs/pull/123/head old-oid=...
    -
    -        # NO, I reject it.
    -        ng <reference> <error message>
    -
    -    Parsing the extended status of the report lines for "send-pack" is not
    -    implemented in this commit, so we can make a compatible test on old
    -    version of a Git client.
    +    The reporting function from "receive-pack" to "send-pack" will be
    +    extended in latter commit just like what the "proc-receive" hook reports
    +    to "receive-pack".
     
         Suggested-by: Junio C Hamano <[email-masked]>
         Signed-off-by: Jiang Xin <[email-masked]>
    @@ builtin/receive-pack.c: static void write_head_info(void)
     +#define RUN_PROC_RECEIVE_RETURNED	2
      struct command {
      	struct command *next;
    - 	const char *error_string;
    -+	const char *extended_status;
    +-	const char *error_string;
    ++	struct ref_push_report report;
      	unsigned int skip_update:1,
     -		     did_not_exist:1;
     +		     did_not_exist:1,
    @@ builtin/receive-pack.c: static void write_head_info(void)
      	int index;
      	struct object_id old_oid;
      	struct object_id new_oid;
    +@@ builtin/receive-pack.c: static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
    + 	struct command *cmd = state->cmd;
    + 
    + 	while (cmd &&
    +-	       state->skip_broken && (cmd->error_string || cmd->did_not_exist))
    ++	       state->skip_broken && (cmd->report.error_message || cmd->did_not_exist))
    + 		cmd = cmd->next;
    + 	if (!cmd)
    + 		return -1; /* EOF */
     @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)
      	return finish_command(&proc);
      }
    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)
     +{
     +	struct command *cmd;
     +	struct command *hint = NULL;
    -+	struct strbuf extended_status = STRBUF_INIT;
     +	int code = 0;
    ++	int new_options = 1;
     +
     +	for (;;) {
     +		struct object_id old_oid, new_oid;
    -+		const char *status;
    -+		char *refname;
    -+		char *remains = NULL;
    ++		const char *head;
    ++		const char *refname;
    ++		char *p;
     +
     +		if (packet_reader_read(reader) != PACKET_READ_NORMAL)
     +			break;
    -+		status = reader->line;
    -+		refname = strchr(status, ' ');
    -+		if (refname) {
    -+			*refname++ = '\0';
    -+			remains = strchr(refname, ' ');
    -+			if (remains)
    -+				*remains++ = '\0';
    ++
    ++		head = reader->line;
    ++		p = strchr(head, ' ');
    ++		if (!p) {
    ++			strbuf_addf(errmsg, "proc-receive reported incomplete status line: '%s'\n", head);
    ++			code = -1;
    ++			continue;
     +		}
    -+		if (!refname || strlen(refname) == 0) {
    -+			strbuf_addf(errmsg, "proc-receive expected '<status> <ref> ...', got '%s'\n",
    -+				    reader->line);
    ++		*p++ = '\0';
    ++		if (!strcmp(head, "option")) {
    ++			struct ref_push_report_options *options;
    ++			const char *key, *val;
    ++
    ++			if (!hint) {
    ++			       if (new_options) {
    ++					strbuf_addstr(errmsg, "proc-receive reported 'option' without a matching 'ok/ng' directive\n");
    ++					new_options = 0;
    ++				}
    ++				code = -1;
    ++				continue;
    ++			}
    ++			options = hint->report.options;
    ++			while (options && options->next)
    ++				options = options->next;
    ++			if (new_options) {
    ++				if (!options) {
    ++					hint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));
    ++					options = hint->report.options;
    ++				} else {
    ++					options->next = xcalloc(1, sizeof(struct ref_push_report_options));
    ++					options = options->next;
    ++				}
    ++				new_options = 0;
    ++			}
    ++			assert(options);
    ++			key = p;
    ++			p = strchr(key, ' ');
    ++			if (p)
    ++				*p++ = '\0';
    ++			val = p;
    ++			if (!strcmp(key, "refname"))
    ++				options->ref_name = xstrdup_or_null(val);
    ++			else if (!strcmp(key, "old-oid") && val &&
    ++				 !parse_oid_hex(val, &old_oid, &val))
    ++				options->old_oid = oiddup(&old_oid);
    ++			else if (!strcmp(key, "new-oid") && val &&
    ++				 !parse_oid_hex(val, &new_oid, &val))
    ++				options->new_oid = oiddup(&new_oid);
    ++			else if (!strcmp(key, "forced-update"))
    ++				options->forced_update = 1;
    ++			else if (!strcmp(key, "fall-through"))
    ++				/* Fall through, let 'receive-pack' to execute it. */
    ++				hint->run_proc_receive = 0;
    ++			continue;
    ++		}
    ++
    ++		refname = p;
    ++		p = strchr(refname, ' ');
    ++		if (p)
    ++			*p++ = '\0';
    ++		if (strcmp(head, "ok") && strcmp(head, "ng")) {
    ++			strbuf_addf(errmsg, "proc-receive reported bad status '%s' on ref '%s'\n",
    ++				    head, refname);
     +			code = -1;
     +			continue;
     +		}
    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)
     +		if (!hint) {
     +			strbuf_addf(errmsg, "proc-receive reported status on unknown ref: %s\n",
     +				    refname);
    ++			code = -1;
     +			continue;
     +		}
     +		if (!hint->run_proc_receive) {
     +			strbuf_addf(errmsg, "proc-receive reported status on unexpected ref: %s\n",
     +				    refname);
    ++			code = -1;
     +			continue;
     +		}
    -+		if (!strcmp(status, "ng")) {
    -+			if (remains)
    -+				hint->error_string = xstrdup(remains);
    ++		if (!strcmp(head, "ng")) {
    ++			if (p)
    ++				hint->report.error_message = xstrdup(p);
     +			else
    -+				hint->error_string = "failed";
    ++				hint->report.error_message = "failed";
     +			code = -1;
    -+		} else if (!strcmp("ft", status)) {
    -+			/* Reset "run_proc_receive" field, and continue to run in "receive-pack" */
    -+			hint->run_proc_receive = 0;
    -+		} else if (!strcmp("alt", status)) {
    -+			char *alt_ref = NULL;
    -+			char *p1, *p2;
    -+
    -+			if (!remains) {
    -+				strbuf_addf(errmsg, "proc-receive expected 'alt <ref> <alt-ref> ...', got 'alt %s'\n",
    -+					    refname);
    -+				continue;
    -+			}
    -+			p1 = strchr(remains, '=');
    -+			p2 = strchr(remains, ' ');
    -+			if (!p1) {
    -+				/*
    -+				 * "alt <ref> <alt-ref>", or
    -+				 * "alt <ref> <alt-ref> forced-update"
    -+				 */
    -+				alt_ref = remains;
    -+				remains = p2;
    -+				if (remains)
    -+					*remains++ = '\0';
    -+			} else if (!p2) {
    -+				/* "alt <ref> old-oid=<oid>" */
    -+				alt_ref = NULL;
    -+			} else if (p1 > p2) {
    -+				/* "alt <ref> <alt-ref> old-oid=<oid>" */
    -+				alt_ref = remains;
    -+				remains = p2;
    -+				if (remains)
    -+					*remains++ = '\0';
    -+			} else {
    -+				/* "alt <ref> old-oid=<oid1> new-oid=<oid2>" */
    -+				alt_ref = NULL;
    -+			}
    -+
    -+			if (hint->extended_status)
    -+				strbuf_addstr(&extended_status, hint->extended_status);
    -+
    -+			/*
    -+			 * Already received an "ok <ref>" response, add an
    -+			 * additional "ref=<ref>" field.
    -+			 */
    -+			if (extended_status.len == 0 &&
    -+			    hint->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)
    -+				strbuf_addf(&extended_status, "ref=%s", refname);
    -+
    -+			if (alt_ref)
    -+				strbuf_addf(&extended_status, "%sref=%s",
    -+					    extended_status.len > 0 ? " ": "",
    -+					    alt_ref);
    -+			else if (extended_status.len > 0)
    -+				/*
    -+				 * Start new extended status for refname, if it
    -+				 * is not the first report for this reference.
    -+				 */
    -+				strbuf_addf(&extended_status, " ref=%s", refname);
    -+
    -+			if (remains) {
    -+				const char *val;
    -+				int len;
    -+
    -+				val = parse_feature_value(remains, "old-oid", &len);
    -+				if (val && len > 0)
    -+					if (!parse_oid_hex(val, &old_oid, &val))
    -+						strbuf_addf(&extended_status, "%sold-oid=%s",
    -+							   extended_status.len > 0 ? " ": "",
    -+							   oid_to_hex(&old_oid));
    -+
    -+				val = parse_feature_value(remains, "new-oid", &len);
    -+				if (val && len > 0)
    -+					if (!parse_oid_hex(val, &new_oid, &val))
    -+						strbuf_addf(&extended_status, "%snew-oid=%s",
    -+							   extended_status.len > 0 ? " ": "",
    -+							   oid_to_hex(&new_oid));
    -+
    -+				if (parse_feature_request(remains, "forced-update"))
    -+					strbuf_addf(&extended_status, "%sforced-update",
    -+						    extended_status.len > 0 ? " ": "");
    -+			}
    -+
    -+			if (extended_status.len > 0) {
    -+				free((void *)hint->extended_status);
    -+				hint->extended_status = strbuf_detach(&extended_status, NULL);
    -+			}
    -+		} else if (strcmp("ok", status)) {
    -+			strbuf_addf(errmsg, "proc-receive reported bad status '%s' on ref '%s'\n",
    -+				    status, refname);
    -+			code = -1;
    -+			/* Skip marking it as RUN_PROC_RECEIVE_RETURNED */
    -+			continue;
     +		}
    -+
     +		if (hint->run_proc_receive)
     +			hint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;
    ++		new_options = 1;
     +	}
     +
     +	for (cmd = commands; cmd; cmd = cmd->next)
    -+		if (cmd->run_proc_receive && !cmd->error_string &&
    ++		if (cmd->run_proc_receive && !cmd->report.error_message &&
     +		    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {
    -+		    cmd->error_string = "proc-receive failed to report status";
    ++		    cmd->report.error_message = "proc-receive failed to report status";
     +		    code = -1;
     +		}
    -+
     +	return code;
     +}
     +
    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)
     +	struct packet_reader reader;
     +	struct strbuf cap = STRBUF_INIT;
     +	struct strbuf errmsg = STRBUF_INIT;
    -+	int pr_use_push_options = 0;
    ++	int hook_use_push_options = 0;
     +	int version = 0;
     +	int code;
     +
    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)
     +			if (linelen < reader.pktlen) {
     +				const char *feature_list = reader.line + linelen + 1;
     +				if (parse_feature_request(feature_list, "push-options"))
    -+					pr_use_push_options = 1;
    ++					hook_use_push_options = 1;
     +			}
     +		}
     +	}
    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)
     +
     +	/* Send commands */
     +	for (cmd = commands; cmd; cmd = cmd->next) {
    -+		if (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)
    ++		if (!cmd->run_proc_receive || cmd->skip_update || cmd->report.error_message)
     +			continue;
     +		packet_write_fmt(proc.in, "%s %s %s",
     +				 oid_to_hex(&cmd->old_oid),
    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)
     +	packet_flush(proc.in);
     +
     +	/* Send push options */
    -+	if (pr_use_push_options) {
    ++	if (hook_use_push_options) {
     +		struct string_list_item *item;
     +
     +		for_each_string_list_item(item, push_options)
    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)
      static char *refuse_unconfigured_deny_msg =
      	N_("By default, updating the current branch in a non-bare repository\n"
      	   "is denied, because it will make the index and work tree inconsistent\n"
    +@@ builtin/receive-pack.c: static void run_update_post_hook(struct command *commands)
    + 		return;
    + 
    + 	for (cmd = commands; cmd; cmd = cmd->next) {
    +-		if (cmd->error_string || cmd->did_not_exist)
    ++		if (cmd->report.error_message || cmd->did_not_exist)
    + 			continue;
    + 		if (!proc.args.argc)
    + 			argv_array_push(&proc.args, hook);
    +@@ builtin/receive-pack.c: static void check_aliased_update_internal(struct command *cmd,
    + 	if (!dst_name) {
    + 		rp_error("refusing update to broken symref '%s'", cmd->ref_name);
    + 		cmd->skip_update = 1;
    +-		cmd->error_string = "broken symref";
    ++		cmd->report.error_message = "broken symref";
    + 		return;
    + 	}
    + 	dst_name = strip_namespace(dst_name);
    +@@ builtin/receive-pack.c: static void check_aliased_update_internal(struct command *cmd,
    + 		 find_unique_abbrev(&dst_cmd->old_oid, DEFAULT_ABBREV),
    + 		 find_unique_abbrev(&dst_cmd->new_oid, DEFAULT_ABBREV));
    + 
    +-	cmd->error_string = dst_cmd->error_string =
    ++	cmd->report.error_message = dst_cmd->report.error_message =
    + 		"inconsistent aliased update";
    + }
    + 
    +@@ builtin/receive-pack.c: static void check_aliased_updates(struct command *commands)
    + 	string_list_sort(&ref_list);
    + 
    + 	for (cmd = commands; cmd; cmd = cmd->next) {
    +-		if (!cmd->error_string)
    ++		if (!cmd->report.error_message)
    + 			check_aliased_update(cmd, &ref_list);
    + 	}
    + 
    +@@ builtin/receive-pack.c: static void set_connectivity_errors(struct command *commands,
    + 				     &opt))
    + 			continue;
    + 
    +-		cmd->error_string = "missing necessary objects";
    ++		cmd->report.error_message = "missing necessary objects";
    + 	}
    + }
    + 
    +@@ builtin/receive-pack.c: static void reject_updates_to_hidden(struct command *commands)
    + 	prefix_len = refname_full.len;
    + 
    + 	for (cmd = commands; cmd; cmd = cmd->next) {
    +-		if (cmd->error_string)
    ++		if (cmd->report.error_message)
    + 			continue;
    + 
    + 		strbuf_setlen(&refname_full, prefix_len);
    +@@ builtin/receive-pack.c: static void reject_updates_to_hidden(struct command *commands)
    + 		if (!ref_is_hidden(cmd->ref_name, refname_full.buf))
    + 			continue;
    + 		if (is_null_oid(&cmd->new_oid))
    +-			cmd->error_string = "deny deleting a hidden ref";
    ++			cmd->report.error_message = "deny deleting a hidden ref";
    + 		else
    +-			cmd->error_string = "deny updating a hidden ref";
    ++			cmd->report.error_message = "deny updating a hidden ref";
    + 	}
    + 
    + 	strbuf_release(&refname_full);
    +@@ builtin/receive-pack.c: static void reject_updates_to_hidden(struct command *commands)
    + 
    + static int should_process_cmd(struct command *cmd)
    + {
    +-	return !cmd->error_string && !cmd->skip_update;
    ++	return !cmd->report.error_message && !cmd->skip_update;
    + }
    + 
    + static void warn_if_skipped_connectivity_check(struct command *commands,
     @@ builtin/receive-pack.c: static void execute_commands_non_atomic(struct command *commands,
      	struct strbuf err = STRBUF_INIT;
      
    @@ builtin/receive-pack.c: static void execute_commands_non_atomic(struct command *
      			continue;
      
      		transaction = ref_transaction_begin(&err);
    + 		if (!transaction) {
    + 			rp_error("%s", err.buf);
    + 			strbuf_reset(&err);
    +-			cmd->error_string = "transaction failed to start";
    ++			cmd->report.error_message = "transaction failed to start";
    + 			continue;
    + 		}
    + 
    +-		cmd->error_string = update(cmd, si);
    ++		cmd->report.error_message = update(cmd, si);
    + 
    +-		if (!cmd->error_string
    ++		if (!cmd->report.error_message
    + 		    && ref_transaction_commit(transaction, &err)) {
    + 			rp_error("%s", err.buf);
    + 			strbuf_reset(&err);
    +-			cmd->error_string = "failed to update ref";
    ++			cmd->report.error_message = "failed to update ref";
    + 		}
    + 		ref_transaction_free(transaction);
    + 	}
     @@ builtin/receive-pack.c: static void execute_commands_atomic(struct command *commands,
      	}
      
    @@ builtin/receive-pack.c: static void execute_commands_atomic(struct command *comm
     +		if (!should_process_cmd(cmd) || cmd->run_proc_receive)
      			continue;
      
    - 		cmd->error_string = update(cmd, si);
    +-		cmd->error_string = update(cmd, si);
    ++		cmd->report.error_message = update(cmd, si);
    + 
    +-		if (cmd->error_string)
    ++		if (cmd->report.error_message)
    + 			goto failure;
    + 	}
    + 
    +@@ builtin/receive-pack.c: static void execute_commands_atomic(struct command *commands,
    + 
    + failure:
    + 	for (cmd = commands; cmd; cmd = cmd->next)
    +-		if (!cmd->error_string)
    +-			cmd->error_string = reported_error;
    ++		if (!cmd->report.error_message)
    ++			cmd->report.error_message = reported_error;
    + 
    + cleanup:
    + 	ref_transaction_free(transaction);
     @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,
      	struct iterate_data data;
      	struct async muxer;
    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,
      
      	if (unpacker_error) {
      		for (cmd = commands; cmd; cmd = cmd->next)
    +-			cmd->error_string = "unpacker error";
    ++			cmd->report.error_message = "unpacker error";
    + 		return;
    + 	}
    + 
     @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,
      
      	reject_updates_to_hidden(commands);
    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,
     +
      	if (run_receive_hook(commands, "pre-receive", 0, push_options)) {
      		for (cmd = commands; cmd; cmd = cmd->next) {
    - 			if (!cmd->error_string)
    +-			if (!cmd->error_string)
    +-				cmd->error_string = "pre-receive hook declined";
    ++			if (!cmd->report.error_message)
    ++				cmd->report.error_message = "pre-receive hook declined";
    + 		}
    + 		return;
    + 	}
    +@@ builtin/receive-pack.c: static void execute_commands(struct command *commands,
    + 	 */
    + 	if (tmp_objdir_migrate(tmp_objdir) < 0) {
    + 		for (cmd = commands; cmd; cmd = cmd->next) {
    +-			if (!cmd->error_string)
    +-				cmd->error_string = "unable to migrate objects to permanent storage";
    ++			if (!cmd->report.error_message)
    ++				cmd->report.error_message = "unable to migrate objects to permanent storage";
    + 		}
    + 		return;
    + 	}
     @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,
      	free(head_name_to_free);
      	head_name = head_name_to_free = resolve_refdup("HEAD", 0, NULL, NULL);
    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,
     +	if (run_proc_receive &&
     +	    run_proc_receive_hook(commands, push_options))
     +		for (cmd = commands; cmd; cmd = cmd->next)
    -+			if (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))
    -+				cmd->error_string = "fail to run proc-receive hook";
    ++			if (!cmd->report.error_message  && (cmd->run_proc_receive || use_atomic))
    ++				cmd->report.error_message = "fail to run proc-receive hook";
     +
      	if (use_atomic)
      		execute_commands_atomic(commands, si);
      	else
    +@@ builtin/receive-pack.c: static void update_shallow_info(struct command *commands,
    + 		if (is_null_oid(&cmd->new_oid))
    + 			continue;
    + 		if (ref_status[cmd->index]) {
    +-			cmd->error_string = "shallow update not allowed";
    ++			cmd->report.error_message = "shallow update not allowed";
    + 			cmd->skip_update = 1;
    + 		}
    + 	}
     @@ builtin/receive-pack.c: static void report(struct command *commands, const char *unpack_status)
      	packet_buf_write(&buf, "unpack %s\n",
      			 unpack_status ? unpack_status : "ok");
      	for (cmd = commands; cmd; cmd = cmd->next) {
     -		if (!cmd->error_string)
    --			packet_buf_write(&buf, "ok %s\n",
    ++		if (!cmd->report.error_message)
    + 			packet_buf_write(&buf, "ok %s\n",
     -					 cmd->ref_name);
    --		else
    --			packet_buf_write(&buf, "ng %s %s\n",
    --					 cmd->ref_name, cmd->error_string);
    -+		if (!cmd->error_string) {
    -+			if (cmd->extended_status)
    -+				packet_buf_write(&buf, "ok %s%c%s\n",
    -+						 cmd->ref_name, '\0',
    -+						 cmd->extended_status);
    -+			else
    -+				packet_buf_write(&buf, "ok %s\n",
     +						 cmd->ref_name);
    -+		} else {
    -+			if (cmd->extended_status)
    -+				packet_buf_write(&buf, "ng %s %s%c%s\n",
    -+						 cmd->ref_name, cmd->error_string,
    -+						 '\0', cmd->extended_status);
    -+			else
    -+				packet_buf_write(&buf, "ng %s %s\n",
    -+						 cmd->ref_name, cmd->error_string);
    -+		}
    + 		else
    + 			packet_buf_write(&buf, "ng %s %s\n",
    +-					 cmd->ref_name, cmd->error_string);
    ++					 cmd->ref_name, cmd->report.error_message);
      	}
      	packet_buf_flush(&buf);
      
    +@@ builtin/receive-pack.c: int cmd_receive_pack(int argc, const char **argv, const char *prefix)
    + 		if (!check_cert_push_options(&push_options)) {
    + 			struct command *cmd;
    + 			for (cmd = commands; cmd; cmd = cmd->next)
    +-				cmd->error_string = "inconsistent push options";
    ++				cmd->report.error_message = "inconsistent push options";
    + 		}
    + 
    + 		prepare_shallow_info(&si, &shallow);
    +
    + ## remote.h ##
    +@@ remote.h: int for_each_remote(each_remote_fn fn, void *priv);
    + 
    + int remote_has_url(struct remote *remote, const char *url);
    + 
    ++struct ref_push_report_options {
    ++	char *ref_name;
    ++	struct object_id *old_oid;
    ++	struct object_id *new_oid;
    ++	unsigned int forced_update:1;
    ++	struct ref_push_report_options *next;
    ++};
    ++
    ++struct ref_push_report {
    ++	const char *message;
    ++	const char *error_message;
    ++	struct ref_push_report_options *options;
    ++};
    ++
    + struct ref {
    + 	struct ref *next;
    + 	struct object_id old_oid;
     
      ## t/helper/test-proc-receive.c (new) ##
     @@
    @@ t/t5411/test-0012-no-hook-error--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       next(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: no hook, fail to push special ref (--porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: no hook, fail to push special ref ($PROTOCOL/porcelain)" '
     +	test_must_fail git -C workbench push --porcelain origin \
     +		HEAD:next \
     +		HEAD:refs/for/master/topic \
    @@ t/t5411/test-0012-no-hook-error--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: no hook, all failed for atomic push (--porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: no hook, all failed for atomic push ($PROTOCOL/porcelain)" '
     +	test_must_fail git -C workbench push --porcelain --atomic origin \
     +		$B:master \
     +		HEAD:next \
    @@ t/t5411/test-0013-bad-protocol.sh (new)
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: proc-receive> ok
    -+	remote: error: proc-receive expected "<status> <ref> ...", got "ok"
    ++	remote: error: proc-receive reported incomplete status line: "ok"
     +	To <URL/of/upstream.git>
     +	 ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
     +	EOF
    @@ t/t5411/test-0014-bad-protocol--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/master/topic(A)
    -+test_expect_success "proc-receive: bad protocol (unknown version, --porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)" '
     +	test_must_fail git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0014-bad-protocol--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       next(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: bad protocol (no report, --porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: bad protocol (no report, $PROTOCOL/porcelain)" '
     +	test_must_fail git -C workbench push --porcelain origin \
     +		HEAD:refs/heads/next \
     +		HEAD:refs/for/master/topic >out 2>&1 &&
    @@ t/t5411/test-0014-bad-protocol--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/master/topic
    -+test_expect_success "proc-receive: bad protocol (no ref, --porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)" '
     +	test_must_fail git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic\
     +		>out 2>&1 &&
    @@ t/t5411/test-0014-bad-protocol--porcelain.sh (new)
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: proc-receive> ok
    -+	remote: error: proc-receive expected "<status> <ref> ...", got "ok"
    ++	remote: error: proc-receive reported incomplete status line: "ok"
     +	To <URL/of/upstream.git>
     +	!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)
     +	Done
    @@ t/t5411/test-0014-bad-protocol--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/master/topic
    -+test_expect_success "proc-receive: bad protocol (unknown status, --porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)" '
     +	test_must_fail git -C workbench push --porcelain origin \
     +			HEAD:refs/for/master/topic \
     +			>out 2>&1 &&
    @@ t/t5411/test-0021-report-ng--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/master/topic
    -+test_expect_success "proc-receive: fail to update (ng, no message, --porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)" '
     +	test_must_fail git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0021-report-ng--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/master/topic
    -+test_expect_success "proc-receive: fail to update (ng, with message, --porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)" '
     +	test_must_fail git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0023-report-unexpect-ref--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         : (B)                   refs/for/master/topic
    -+test_expect_success "proc-receive: report unexpected ref (--porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report unexpected ref ($PROTOCOL/porcelain)" '
     +	test_must_fail git -C workbench push --porcelain origin \
     +		$B:refs/heads/master \
     +		HEAD:refs/for/master/topic \
    @@ t/t5411/test-0025-report-unknown-ref--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/a/b/c/my/topic
    -+test_expect_success "proc-receive: report unknown reference (--porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report unknown reference ($PROTOCOL/porcelain)" '
     +	test_must_fail git -C workbench push --porcelain origin \
     +		HEAD:refs/for/a/b/c/my/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0027-push-options--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push -o ...  :                       refs/for/master/topic
    -+test_expect_success "proc-receive: not support push options (--porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: not support push options ($PROTOCOL/porcelain)" '
     +	test_must_fail git -C workbench push \
     +		--porcelain \
     +		-o issue=123 \
    @@ t/t5411/test-0027-push-options--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push -o ...  :                       next(A)  refs/for/master/topic
    -+test_expect_success "proc-receive: push with options (--porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: push with options ($PROTOCOL/porcelain)" '
     +	git -C workbench push \
     +		--porcelain \
     +		--atomic \
    @@ t/t5411/test-0031-report-ok--porcelain.sh (new)
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/master/topic
    -+test_expect_success "proc-receive: ok (--porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: ok ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0031-report-ok--porcelain.sh (new)
     +	test_cmp expect actual
     +'
     
    - ## t/t5411/test-0032-report-alt.sh (new) ##
    + ## t/t5411/test-0032-report-with-options.sh (new) ##
     @@
    -+test_expect_success "setup proc-receive hook (alt <ref>, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (option without matching ok, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic"
    ++		-r "option refname refs/pull/123/head" \
    ++		-r "option old-oid $B"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report alt (alt <ref>, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report option without matching ok ($PROTOCOL)" '
     +	test_must_fail git -C workbench push origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0032-report-alt.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic
    -+	remote: error: proc-receive expected "alt <ref> <alt-ref> ...", got "alt refs/for/master/topic"
    ++	remote: proc-receive> option refname refs/pull/123/head
    ++	remote: proc-receive> option old-oid <COMMIT-B>
    ++	remote: error: proc-receive reported "option" without a matching "ok/ng" directive
     +	To <URL/of/upstream.git>
     +	 ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
     +	EOF
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (alt <ref> <alt-ref>, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (option refname, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic refs/pull/123/head"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/pull/123/head"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report alt (alt <ref> <alt-ref>, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report option refname ($PROTOCOL)" '
     +	git -C workbench push origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0032-report-alt.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/pull/123/head
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/pull/123/head
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0032-report-alt.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (alt <ref> <alt-ref> forced-update, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (option refname and forced-update, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic refs/pull/123/head forced-update"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/pull/123/head" \
    ++		-r "option forced-update"
     +	EOF
     +'
    -+
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report alt (alt <ref> <alt-ref> forced-update, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report option refname and forced-update ($PROTOCOL)" '
     +	git -C workbench push origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0032-report-alt.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/pull/123/head forced-update
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/pull/123/head
    ++	remote: proc-receive> option forced-update
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0032-report-alt.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (alt <ref> <alt-ref> old-oid=X, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (option refname and old-oid, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic refs/pull/123/head old-oid=$B"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/pull/123/head" \
    ++		-r "option old-oid $B"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL)" '
     +	git -C workbench push origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0032-report-alt.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/pull/123/head
    ++	remote: proc-receive> option old-oid <COMMIT-B>
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0032-report-alt.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (alt <ref> old-oid=X, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (option old-oid, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic old-oid=$B"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option old-oid $B"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report alt (alt <ref> old-oid=X, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report option old-oid ($PROTOCOL)" '
     +	git -C workbench push origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0032-report-alt.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option old-oid <COMMIT-B>
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0032-report-alt.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (alt <ref> old-oid=X new-oid=Y, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (option old-oid and new-oid, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic old-oid=$A new-oid=$B"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report option old-oid and new-oid ($PROTOCOL)" '
     +	git -C workbench push origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0032-report-alt.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0032-report-alt.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (with multiple alt reports, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (report with multiple rewrites, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
     +		-r "ok refs/for/a/b/c/topic" \
    -+		-r "alt refs/for/next/topic refs/pull/123/head" \
    -+		-r "alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A"
    ++		-r "ok refs/for/next/topic" \
    ++		-r "option refname refs/pull/123/head" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/pull/124/head" \
    ++		-r "option old-oid $B" \
    ++		-r "option forced-update" \
    ++		-r "option new-oid $A"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: with multiple alt reports ($PROTOCOL)" '
    ++test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL)" '
     +	git -C workbench push origin \
     +		HEAD:refs/for/next/topic \
     +		HEAD:refs/for/a/b/c/topic \
    @@ t/t5411/test-0032-report-alt.sh (new)
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: proc-receive> ok refs/for/a/b/c/topic
    -+	remote: proc-receive> alt refs/for/next/topic refs/pull/123/head
    -+	remote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>
    ++	remote: proc-receive> ok refs/for/next/topic
    ++	remote: proc-receive> option refname refs/pull/123/head
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/pull/124/head
    ++	remote: proc-receive> option old-oid <COMMIT-B>
    ++	remote: proc-receive> option forced-update
    ++	remote: proc-receive> option new-oid <COMMIT-A>
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
    @@ t/t5411/test-0032-report-alt.sh (new)
     +	test_cmp expect actual
     +'
     
    - ## t/t5411/test-0033-report-alt--porcelain.sh (new) ##
    + ## t/t5411/test-0033-report-with-options--porcelain.sh (new) ##
     @@
    -+test_expect_success "setup proc-receive hook (alt <ref>, --porcelain, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (option without matching ok, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic"
    ++		-r "option refname refs/pull/123/head" \
    ++		-r "option old-oid $B"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report alt (alt <ref>, --porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report option without matching ok ($PROTOCOL/porcelain)" '
     +	test_must_fail git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic
    -+	remote: error: proc-receive expected "alt <ref> <alt-ref> ...", got "alt refs/for/master/topic"
    ++	remote: proc-receive> option refname refs/pull/123/head
    ++	remote: proc-receive> option old-oid <COMMIT-B>
    ++	remote: error: proc-receive reported "option" without a matching "ok/ng" directive
     +	To <URL/of/upstream.git>
     +	!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)
     +	Done
    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (alt <ref> <alt-ref>, --porcelain, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (option refname, $PROTOCOL/porcelain)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic refs/pull/123/head"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/pull/123/head"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report alt (alt <ref> <alt-ref>, --porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report option refname ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/pull/123/head
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/pull/123/head
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (alt <ref> <alt-ref> forced-update, --porcelain, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (option refname and forced-update, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic refs/pull/123/head forced-update"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/pull/123/head" \
    ++		-r "option forced-update"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report alt (alt <ref> <alt-ref> forced-update, --porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report option refname and forced-update ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/pull/123/head forced-update
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/pull/123/head
    ++	remote: proc-receive> option forced-update
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (alt <ref> <alt-ref> old-oid=X, --porcelain, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (option refname and old-oid, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic refs/pull/123/head old-oid=$B"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/pull/123/head" \
    ++		-r "option old-oid $B"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, --porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/pull/123/head
    ++	remote: proc-receive> option old-oid <COMMIT-B>
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (alt <ref> old-oid=X, --porcelain, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (option old-oid, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic old-oid=$B"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option old-oid $B"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report alt (alt <ref> old-oid=X, --porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report option old-oid ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option old-oid <COMMIT-B>
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (alt <ref> old-oid=X new-oid=Y, --porcelain, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (option old-oid and new-oid, $PROTOCOL/porcelain)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic old-oid=$A new-oid=$B"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, --porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report option old-oid and new-oid ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (with multiple alt reports, --porcelain, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (report with multiple rewrites, $PROTOCOL/porcelain)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
     +		-r "ok refs/for/a/b/c/topic" \
    -+		-r "alt refs/for/next/topic refs/pull/123/head" \
    -+		-r "alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A"
    ++		-r "ok refs/for/next/topic" \
    ++		-r "option refname refs/pull/123/head" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/pull/124/head" \
    ++		-r "option old-oid $B" \
    ++		-r "option forced-update" \
    ++		-r "option new-oid $A"
    ++
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: with multiple alt reports (--porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain origin \
     +		HEAD:refs/for/next/topic \
     +		HEAD:refs/for/a/b/c/topic \
    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: proc-receive> ok refs/for/a/b/c/topic
    -+	remote: proc-receive> alt refs/for/next/topic refs/pull/123/head
    -+	remote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>
    ++	remote: proc-receive> ok refs/for/next/topic
    ++	remote: proc-receive> option refname refs/pull/123/head
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/pull/124/head
    ++	remote: proc-receive> option old-oid <COMMIT-B>
    ++	remote: proc-receive> option forced-update
    ++	remote: proc-receive> option new-oid <COMMIT-A>
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
    @@ t/t5411/test-0034-report-ft.sh (new)
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "ft refs/for/master/topic"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option fall-through"
     +	EOF
     +'
     +
    @@ t/t5411/test-0034-report-ft.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
    -+	remote: proc-receive> ft refs/for/master/topic
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option fall-through
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0034-report-ft.sh (new)
     
      ## t/t5411/test-0035-report-ft--porcelain.sh (new) ##
     @@
    -+test_expect_success "setup proc-receive hook (ft, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (fall-through, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "ft refs/for/master/topic"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option fall-through"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/master/topic(B)
    -+test_expect_success "proc-receive: fall throught, let receive-pack to execute (--porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: fall throught, let receive-pack to execute ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain origin \
     +		$B:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0035-report-ft--porcelain.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
    -+	remote: proc-receive> ft refs/for/master/topic
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option fall-through
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0035-report-ft--porcelain.sh (new)
     +	git -C "$upstream" update-ref -d refs/for/master/topic
     +'
     
    - ## t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new) ##
    + ## t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh (new) ##
     @@
    -+test_expect_success "setup proc-receive hook (multiple alt, no alt-ref for the 1st resp, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic old-oid=$A new-oid=$B" \
    -+		-r "alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A" \
    -+		-r "alt refs/for/master/topic refs/changes/25/125/1 old-oid=$A new-oid=$B"
    -+	EOF
    -+'
    -+
    -+# Refs of upstream : master(A)
    -+# Refs of workbench: master(A)  tags/v123
    -+# git push         :                       refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report multiple alt, no alt-ref for the 1st resp ($PROTOCOL)" '
    -+	git -C workbench push origin \
    -+		HEAD:refs/for/master/topic \
    -+		>out 2>&1 &&
    -+	make_user_friendly_and_stable_output <out >actual &&
    -+	cat >expect <<-EOF &&
    -+	remote: # pre-receive hook
    -+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: # proc-receive hook
    -+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    -+	remote: # post-receive hook
    -+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	To <URL/of/upstream.git>
    -+	 * [new reference] HEAD -> refs/for/master/topic
    -+	EOF
    -+	test_cmp expect actual &&
    -+	git -C "$upstream" show-ref >out &&
    -+	make_user_friendly_and_stable_output <out >actual &&
    -+	cat >expect <<-EOF &&
    -+	<COMMIT-A> refs/heads/master
    -+	EOF
    -+	test_cmp expect actual
    -+'
    -+
    -+test_expect_success "setup proc-receive hook (multiple alt, no alt-ref for the 2nd resp, $PROTOCOL)" '
    -+	write_script "$upstream/hooks/proc-receive" <<-EOF
    -+	printf >&2 "# proc-receive hook\n"
    -+	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A" \
    -+		-r "alt refs/for/master/topic old-oid=$A new-oid=$B" \
    -+		-r "alt refs/for/master/topic refs/changes/25/125/1 old-oid=$B new-oid=$A forced-update"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/changes/24/124/1" \
    ++		-r "option old-oid $ZERO_OID" \
    ++		-r "option new-oid $A" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/changes/25/125/1" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report multiple alt, no alt-ref for the 2nd resp ($PROTOCOL)" '
    ++test_expect_success "proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL)" '
     +	git -C workbench push origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>
    -+	remote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/changes/24/124/1
    ++	remote: proc-receive> option old-oid <ZERO-OID>
    ++	remote: proc-receive> option new-oid <COMMIT-A>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/changes/25/125/1
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (ok and alt for the same ref, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
     +		-r "ok refs/for/master/topic" \
    -+		-r "alt refs/for/master/topic refs/changes/24/124/1 new-oid=$B old-oid=$A"
    ++		-r "option refname refs/changes/24/124/1" \
    ++		-r "option old-oid $ZERO_OID" \
    ++		-r "option new-oid $A" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/changes/25/125/1" \
    ++		-r "option old-oid $B" \
    ++		-r "option new-oid $A" \
    ++		-r "option forced-update"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report ok and alt for the same ref ($PROTOCOL)" '
    ++test_expect_success "proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL)" '
     +	git -C workbench push origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new)
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: proc-receive> ok refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 new-oid=<COMMIT-B> old-oid=<COMMIT-A>
    ++	remote: proc-receive> option refname refs/changes/24/124/1
    ++	remote: proc-receive> option old-oid <ZERO-OID>
    ++	remote: proc-receive> option new-oid <COMMIT-A>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/changes/25/125/1
    ++	remote: proc-receive> option old-oid <COMMIT-B>
    ++	remote: proc-receive> option new-oid <COMMIT-A>
    ++	remote: proc-receive> option forced-update
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (multiple responses, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic refs/changes/23/123/1" \
    -+		-r "alt refs/for/master/topic refs/changes/24/124/2 old-oid=$A new-oid=$B"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/changes/23/123/1" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/changes/24/124/2" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report multiple response ($PROTOCOL)" '
    ++test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL)" '
     +	git -C workbench push origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/23/123/1
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/24/124/2 old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/changes/23/123/1
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/changes/24/124/2
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new)
     +	test_cmp expect actual
     +'
     
    - ## t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh (new) ##
    + ## t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh (new) ##
     @@
    -+test_expect_success "setup proc-receive hook (multiple alt, no alt-ref for the 1st resp, $PROTOCOL)" '
    -+	write_script "$upstream/hooks/proc-receive" <<-EOF
    -+	printf >&2 "# proc-receive hook\n"
    -+	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic old-oid=$A new-oid=$B" \
    -+		-r "alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A" \
    -+		-r "alt refs/for/master/topic refs/changes/25/125/1 old-oid=$A new-oid=$B"
    -+	EOF
    -+'
    -+
    -+# Refs of upstream : master(A)
    -+# Refs of workbench: master(A)  tags/v123
    -+# git push         :                       refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report multiple alt, no alt-ref for the 1st resp (--porcelain, $PROTOCOL)" '
    -+	git -C workbench push --porcelain origin \
    -+		HEAD:refs/for/master/topic \
    -+		>out 2>&1 &&
    -+	make_user_friendly_and_stable_output <out >actual &&
    -+	cat >expect <<-EOF &&
    -+	remote: # pre-receive hook
    -+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: # proc-receive hook
    -+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    -+	remote: # post-receive hook
    -+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	To <URL/of/upstream.git>
    -+	*    HEAD:refs/for/master/topic    [new reference]
    -+	Done
    -+	EOF
    -+	test_cmp expect actual &&
    -+	git -C "$upstream" show-ref >out &&
    -+	make_user_friendly_and_stable_output <out >actual &&
    -+	cat >expect <<-EOF &&
    -+	<COMMIT-A> refs/heads/master
    -+	EOF
    -+	test_cmp expect actual
    -+'
    -+
    -+test_expect_success "setup proc-receive hook (multiple alt, no alt-ref for the 2nd resp, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A" \
    -+		-r "alt refs/for/master/topic old-oid=$A new-oid=$B" \
    -+		-r "alt refs/for/master/topic refs/changes/25/125/1 old-oid=$B new-oid=$A forced-update"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/changes/24/124/1" \
    ++		-r "option old-oid $ZERO_OID" \
    ++		-r "option new-oid $A" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/changes/25/125/1" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report multiple alt, no alt-ref for the 2nd resp (--porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>
    -+	remote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/changes/24/124/1
    ++	remote: proc-receive> option old-oid <ZERO-OID>
    ++	remote: proc-receive> option new-oid <COMMIT-A>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/changes/25/125/1
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (ok and alt for the same ref, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
     +		-r "ok refs/for/master/topic" \
    -+		-r "alt refs/for/master/topic refs/changes/24/124/1 new-oid=$B old-oid=$A"
    ++		-r "option refname refs/changes/24/124/1" \
    ++		-r "option old-oid $ZERO_OID" \
    ++		-r "option new-oid $A" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/changes/25/125/1" \
    ++		-r "option old-oid $B" \
    ++		-r "option new-oid $A" \
    ++		-r "option forced-update"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report ok and alt for the same ref (--porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh (new)
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: proc-receive> ok refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 new-oid=<COMMIT-B> old-oid=<COMMIT-A>
    ++	remote: proc-receive> option refname refs/changes/24/124/1
    ++	remote: proc-receive> option old-oid <ZERO-OID>
    ++	remote: proc-receive> option new-oid <COMMIT-A>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/changes/25/125/1
    ++	remote: proc-receive> option old-oid <COMMIT-B>
    ++	remote: proc-receive> option new-oid <COMMIT-A>
    ++	remote: proc-receive> option forced-update
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh (new)
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success "setup proc-receive hook (multiple responses, $PROTOCOL)" '
    ++test_expect_success "setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)" '
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "alt refs/for/master/topic refs/changes/23/123/1" \
    -+		-r "alt refs/for/master/topic refs/changes/24/124/2 old-oid=$A new-oid=$B"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/changes/23/123/1" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/changes/24/124/2" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         :                       refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report multiple response (--porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain origin \
     +		HEAD:refs/for/master/topic \
     +		>out 2>&1 &&
    @@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh (new)
     +	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: # proc-receive hook
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/23/123/1
    -+	remote: proc-receive> alt refs/for/master/topic refs/changes/24/124/2 old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/changes/23/123/1
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/changes/24/124/2
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
     +	remote: # post-receive hook
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	To <URL/of/upstream.git>
    @@ t/t5411/test-0038-report-mixed-refs.sh (new)
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
     +		-r "ok refs/for/next/topic" \
    -+		-r "alt refs/for/master/topic refs/for/master/topic old-oid=$A new-oid=$B"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/for/master/topic" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B"
     +	EOF
     +'
     +
    @@ t/t5411/test-0038-report-mixed-refs.sh (new)
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: proc-receive> ok refs/for/next/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/for/master/topic
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
     +	remote: # post-receive hook
     +	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar
    @@ t/t5411/test-0038-report-mixed-refs.sh (new)
     +		git update-ref -d refs/heads/bar &&
     +		git update-ref -d refs/heads/baz
     +	)
    -+
     +'
     
      ## t/t5411/test-0039-report-mixed-refs--porcelain.sh (new) ##
    @@ t/t5411/test-0039-report-mixed-refs--porcelain.sh (new)
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
     +		-r "ok refs/for/next/topic" \
    -+		-r "alt refs/for/master/topic refs/for/master/topic old-oid=$A new-oid=$B"
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/for/master/topic" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B"
     +	EOF
     +'
     +
     +# Refs of upstream : master(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)
    -+test_expect_success "proc-receive: report update of mixed refs (--porcelain, $PROTOCOL)" '
    ++test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain origin \
     +		$B:refs/heads/master \
     +		HEAD:refs/heads/bar \
    @@ t/t5411/test-0039-report-mixed-refs--porcelain.sh (new)
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: proc-receive> ok refs/for/next/topic
    -+	remote: proc-receive> alt refs/for/master/topic refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/for/master/topic
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
     +	remote: # post-receive hook
     +	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
     +	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar
4:  d01004ff52 < -:  ---------- send-pack: extension for client-side status report
5:  583b5bdfcd < -:  ---------- receive-pack: feed extended_status to post-receive
-:  ---------- > 3:  267da25afd New capability "report-status-v2" for git-push
-:  ---------- > 4:  2cc92d9383 receive-pack: feed report options to post-receive
6:  860be91506 = 5:  47c5e59c9a refs.c: refactor to reuse ref_is_hidden()
7:  9489db75e6 ! 6:  37e041156f receive-pack: new config receive.procReceiveRefs
    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,
     +	if (proc_receive_refs.nr > 0) {
     +		struct strbuf refname_full = STRBUF_INIT;
     +		size_t prefix_len;
    ++
    ++		strbuf_addstr(&refname_full, get_git_namespace());
    ++		prefix_len = refname_full.len;
      
     -		/* TODO: replace the fixed prefix by looking up git config variables. */
     -		if (!strncmp(cmd->ref_name, "refs/for/", 9)) {
     -			cmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;
     -			run_proc_receive = 1;
    -+		strbuf_addstr(&refname_full, get_git_namespace());
    -+		prefix_len = refname_full.len;
    -+
     +		for (cmd = commands; cmd; cmd = cmd->next) {
     +			if (!should_process_cmd(cmd))
     +				continue;
    @@ t/t5411/test-0040-process-all-refs.sh (new)
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "ft refs/heads/master" \
    -+		-r "ft refs/heads/foo" \
    -+		-r "ft refs/heads/bar" \
    -+		-r "alt refs/for/master/topic refs/pull/123/head old-oid=$A new-oid=$B " \
    -+		-r "alt refs/for/next/topic refs/pull/124/head old-oid=$B new-oid=$A forced-update"
    ++		-r "ok refs/heads/master" \
    ++		-r "option fall-through" \
    ++		-r "ok refs/heads/foo" \
    ++		-r "option fall-through" \
    ++		-r "ok refs/heads/bar" \
    ++		-r "option fall-through" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/pull/123/head" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B" \
    ++		-r "ok refs/for/next/topic" \
    ++		-r "option refname refs/pull/124/head" \
    ++		-r "option old-oid $B" \
    ++		-r "option new-oid $A" \
    ++		-r "option forced-update"
     +	EOF
     +'
     +
    @@ t/t5411/test-0040-process-all-refs.sh (new)
     +	remote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
    -+	remote: proc-receive> ft refs/heads/master
    -+	remote: proc-receive> ft refs/heads/foo
    -+	remote: proc-receive> ft refs/heads/bar
    -+	remote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    -+	remote: proc-receive> alt refs/for/next/topic refs/pull/124/head old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update
    ++	remote: proc-receive> ok refs/heads/master
    ++	remote: proc-receive> option fall-through
    ++	remote: proc-receive> ok refs/heads/foo
    ++	remote: proc-receive> option fall-through
    ++	remote: proc-receive> ok refs/heads/bar
    ++	remote: proc-receive> option fall-through
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/pull/123/head
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
    ++	remote: proc-receive> ok refs/for/next/topic
    ++	remote: proc-receive> option refname refs/pull/124/head
    ++	remote: proc-receive> option old-oid <COMMIT-B>
    ++	remote: proc-receive> option new-oid <COMMIT-A>
    ++	remote: proc-receive> option forced-update
     +	remote: # post-receive hook
     +	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
     +	remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
    @@ t/t5411/test-0041-process-all-refs--porcelain.sh (new)
     +	write_script "$upstream/hooks/proc-receive" <<-EOF
     +	printf >&2 "# proc-receive hook\n"
     +	test-tool proc-receive -v \
    -+		-r "ft refs/heads/master" \
    -+		-r "ft refs/heads/foo" \
    -+		-r "ft refs/heads/bar" \
    -+		-r "alt refs/for/master/topic refs/pull/123/head old-oid=$A new-oid=$B " \
    -+		-r "alt refs/for/next/topic refs/pull/124/head old-oid=$B new-oid=$A forced-update"
    ++		-r "ok refs/heads/master" \
    ++		-r "option fall-through" \
    ++		-r "ok refs/heads/foo" \
    ++		-r "option fall-through" \
    ++		-r "ok refs/heads/bar" \
    ++		-r "option fall-through" \
    ++		-r "ok refs/for/master/topic" \
    ++		-r "option refname refs/pull/123/head" \
    ++		-r "option old-oid $A" \
    ++		-r "option new-oid $B" \
    ++		-r "ok refs/for/next/topic" \
    ++		-r "option refname refs/pull/124/head" \
    ++		-r "option old-oid $B" \
    ++		-r "option new-oid $A" \
    ++		-r "option forced-update"
     +	EOF
     +'
     +
     +# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)
     +# Refs of workbench: master(A)  tags/v123
     +# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)
    -+test_expect_success "proc-receive: process all refs (--porcelain) ($PROTOCOL)" '
    ++test_expect_success "proc-receive: process all refs ($PROTOCOL/porcelain)" '
     +	git -C workbench push --porcelain -f origin \
     +		HEAD:refs/heads/master \
     +		:refs/heads/foo \
    @@ t/t5411/test-0041-process-all-refs--porcelain.sh (new)
     +	remote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
     +	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
    -+	remote: proc-receive> ft refs/heads/master
    -+	remote: proc-receive> ft refs/heads/foo
    -+	remote: proc-receive> ft refs/heads/bar
    -+	remote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-A> new-oid=<COMMIT-B>
    -+	remote: proc-receive> alt refs/for/next/topic refs/pull/124/head old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update
    ++	remote: proc-receive> ok refs/heads/master
    ++	remote: proc-receive> option fall-through
    ++	remote: proc-receive> ok refs/heads/foo
    ++	remote: proc-receive> option fall-through
    ++	remote: proc-receive> ok refs/heads/bar
    ++	remote: proc-receive> option fall-through
    ++	remote: proc-receive> ok refs/for/master/topic
    ++	remote: proc-receive> option refname refs/pull/123/head
    ++	remote: proc-receive> option old-oid <COMMIT-A>
    ++	remote: proc-receive> option new-oid <COMMIT-B>
    ++	remote: proc-receive> ok refs/for/next/topic
    ++	remote: proc-receive> option refname refs/pull/124/head
    ++	remote: proc-receive> option old-oid <COMMIT-B>
    ++	remote: proc-receive> option new-oid <COMMIT-A>
    ++	remote: proc-receive> option forced-update
     +	remote: # post-receive hook
     +	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
     +	remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
8:  187a723d71 ! 7:  5e08df63d2 doc: add documentation for the proc-receive hook
    @@ Documentation/githooks.txt: The default 'update' hook, when enabled--and with
      `hooks.allowunannotated` config option unset or set to false--prevents
      unannotated tags to be pushed.
      
    ++[[proc-receive]]
     +proc-receive
     +~~~~~~~~~~~~
     +
    @@ Documentation/githooks.txt: The default 'update' hook, when enabled--and with
     +    # NO, I reject it.
     +    H: PKT-LINE(ng <ref> <reason>)
     +    # Fall through, let 'receive-pack' to execute it.
    -+    H: PKT-LINE(ft <ref>)
    ++    H: PKT-LINE(ok <ref>)
    ++    H: PKT-LINE(option fall-through)
     +    # OK, but has an alternate reference.  The alternate reference name
    -+    # is given in the third parameter, and other status can be given in
    -+    # key-value pairs.
    -+    H: PKT-LINE(alt <ref> <alt-ref> old-oid=<old-oid> new-oid=<new-oid>
    -+                forced-update)
    ++    # and other status can be given in options
    ++    H: PKT-LINE(ok <ref>)
    ++    H: PKT-LINE(option refname <refname>)
    ++    H: PKT-LINE(option old-oid <old-oid>)
    ++    H: PKT-LINE(option new-oid <new-oid>)
    ++    H: PKT-LINE(option forced-update)
     +    H: ... ...
     +    H: flush-pkt
     +
     +Each command for the 'proc-receive' hook may point to a pseudo-reference
     +and always has a zero-old as its old-oid, while the 'proc-receive' hook
     +may update an alternate reference and the alternate reference may exist
    -+already with a non-zero old-oid.  For this case, this hook may return
    -+different OID and different reference name as extended status of the
    -+report line.
    ++already with a non-zero old-oid.  For this case, this hook will use
    ++"option" directives to report extended attributes for the reference given
    ++by the above "ok/ng" directive.
     +
     +The report of the commands of this hook should have the same order as
     +the input.  The exit status of the 'proc-receive' hook only determines

----

Jiang Xin (7):
  transport: not report a non-head push as a branch
  receive-pack: add new proc-receive hook
  New capability "report-status-v2" for git-push
  receive-pack: feed report options to post-receive
  refs.c: refactor to reuse ref_is_hidden()
  receive-pack: new config receive.procReceiveRefs
  doc: add documentation for the proc-receive hook

 Documentation/config/receive.txt              |  14 +
 Documentation/githooks.txt                    |  62 +++
 Makefile                                      |   1 +
 builtin/receive-pack.c                        | 459 ++++++++++++++++--
 builtin/send-pack.c                           |  21 +-
 refs.c                                        |  13 +-
 refs.h                                        |   1 +
 remote.c                                      |   7 +-
 remote.h                                      |  16 +-
 send-pack.c                                   |  96 +++-
 t/helper/test-proc-receive.c                  | 175 +++++++
 t/helper/test-tool.c                          |   1 +
 t/helper/test-tool.h                          |   1 +
 t/t5411-proc-receive-hook.sh                  | 106 ++++
 t/t5411/common-functions.sh                   |  56 +++
 t/t5411/test-0000-standard-git-push.sh        |  84 ++++
 .../test-0001-standard-git-push--porcelain.sh |  86 ++++
 t/t5411/test-0002-pre-receive-declined.sh     |  33 ++
 ...st-0003-pre-receive-declined--porcelain.sh |  34 ++
 t/t5411/test-0010-proc-receive-settings.sh    |   7 +
 t/t5411/test-0011-no-hook-error.sh            |  64 +++
 t/t5411/test-0012-no-hook-error--porcelain.sh |  66 +++
 t/t5411/test-0013-bad-protocol.sh             | 225 +++++++++
 t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++
 t/t5411/test-0020-report-ng.sh                |  67 +++
 t/t5411/test-0021-report-ng--porcelain.sh     |  69 +++
 t/t5411/test-0022-report-unexpect-ref.sh      |  45 ++
 ...est-0023-report-unexpect-ref--porcelain.sh |  46 ++
 t/t5411/test-0024-report-unknown-ref.sh       |  34 ++
 ...test-0025-report-unknown-ref--porcelain.sh |  35 ++
 t/t5411/test-0026-push-options.sh             |  79 +++
 t/t5411/test-0027-push-options--porcelain.sh  |  82 ++++
 t/t5411/test-0030-report-ok.sh                |  35 ++
 t/t5411/test-0031-report-ok--porcelain.sh     |  36 ++
 t/t5411/test-0032-report-with-options.sh      | 256 ++++++++++
 ...est-0033-report-with-options--porcelain.sh | 265 ++++++++++
 t/t5411/test-0034-report-ft.sh                |  44 ++
 t/t5411/test-0035-report-ft--porcelain.sh     |  45 ++
 ...t-0036-report-multi-rewrite-for-one-ref.sh | 169 +++++++
 ...rt-multi-rewrite-for-one-ref--porcelain.sh | 172 +++++++
 t/t5411/test-0038-report-mixed-refs.sh        |  79 +++
 .../test-0039-report-mixed-refs--porcelain.sh |  81 ++++
 t/t5411/test-0040-process-all-refs.sh         | 113 +++++
 .../test-0041-process-all-refs--porcelain.sh  | 114 +++++
 t/t5516-fetch-push.sh                         |   2 +-
 transport-helper.c                            |  53 +-
 transport.c                                   | 121 +++--
 47 files changed, 3737 insertions(+), 93 deletions(-)
 create mode 100644 t/helper/test-proc-receive.c
 create mode 100755 t/t5411-proc-receive-hook.sh
 create mode 100644 t/t5411/common-functions.sh
 create mode 100644 t/t5411/test-0000-standard-git-push.sh
 create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh
 create mode 100644 t/t5411/test-0002-pre-receive-declined.sh
 create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh
 create mode 100644 t/t5411/test-0010-proc-receive-settings.sh
 create mode 100644 t/t5411/test-0011-no-hook-error.sh
 create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh
 create mode 100644 t/t5411/test-0013-bad-protocol.sh
 create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh
 create mode 100644 t/t5411/test-0020-report-ng.sh
 create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh
 create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh
 create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh
 create mode 100644 t/t5411/test-0024-report-unknown-ref.sh
 create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh
 create mode 100644 t/t5411/test-0026-push-options.sh
 create mode 100644 t/t5411/test-0027-push-options--porcelain.sh
 create mode 100644 t/t5411/test-0030-report-ok.sh
 create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh
 create mode 100644 t/t5411/test-0032-report-with-options.sh
 create mode 100644 t/t5411/test-0033-report-with-options--porcelain.sh
 create mode 100644 t/t5411/test-0034-report-ft.sh
 create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh
 create mode 100644 t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh
 create mode 100644 t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh
 create mode 100644 t/t5411/test-0038-report-mixed-refs.sh
 create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh
 create mode 100644 t/t5411/test-0040-process-all-refs.sh
 create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh

-- 
2.26.1.120.g98702cf3e9